Ffmpeg视频开发教程(七)——基于ffmpeg4.0生成模拟yuv数据和模拟音频数据再合成为mp4文件

2023-11-20

本文主要实现使用最新版的ffmpeg生成模拟yuv数据和模拟音频数据再合成为mp4文件。重要代码都是来自官方,稳定性可靠。

本文程序的环境搭建参考我的第一篇FFMPEG教程:https://blog.csdn.net/zhangamxqun/article/details/80304494

不知道如何生成yuv帧数据?不知道如何生成音频数据? 不知道如何合成mp4文件?甚至不知道如何使用最新版的ffmpeg?编译不过,一堆错误,快要抓狂?


使用本文的源码,配置好环境,直接编译通过,运行OK.

下面就是核心代码和最详细的注释:

/** 
根据官方示例,实现FFMPEG4.0生成模拟mp4文件,作者自己测试正确可用
解决了windows下的编译问题,生成的结果文件为generated.mp4,因为码率设置的比较低,清晰度不是很高。
中文注释非常详细,涉及的知识点:yuv格式,aac格式,时间戳,时间基,帧率,重采样,编码,合成等等。信息量很大。
作者:明天继续
使用的ffmpeg版本:ffmpeg-20180508-293a6e8-win32 
开发工具:vs2012 
**/  

#include "stdafx.h"  
#include <Windows.h>
#include <wingdi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <math.h>

#if _MSC_VER  
#define snprintf sprintf_s  //解决windows下编译问题
#define PRIx64       "I64x"  
#define PRIX64       "I64X"  
#define PRId64       "I64d" 
#endif  

#define  __STDC_CONSTANT_MACROS //解决windows下编译问题

extern "C" {  
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>

}  

//lib库的路径请按照自己的项目重新设置
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avcodec.lib")  
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avformat.lib")  
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avfilter.lib")  
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/avutil.lib")  
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/swscale.lib") 
#pragma comment(lib,"../../ffmpeg-20180508-293a6e8-win32-dev/lib/swresample.lib")



#define STREAM_DURATION   10.0
#define STREAM_FRAME_RATE 25 /* 每秒25帧*/
#define STREAM_PIX_FMT    AV_PIX_FMT_YUV420P /* 默认的像素格式 */

#define SCALE_FLAGS SWS_BICUBIC


char tempts[AV_TS_MAX_STRING_SIZE] = {0};
char temperro[AV_ERROR_MAX_STRING_SIZE]={0};

char * av_ts2str_f(int64_t ts)
{
	return av_ts_make_string(tempts, ts);
}

char* av_ts2timestr_f(int64_t ts, AVRational *tb) 
{	
	return av_ts_make_time_string(tempts, ts, tb);
}

char* av_err2str_f(int64_t errnum)
{
	return av_make_error_string(temperro, AV_ERROR_MAX_STRING_SIZE, errnum);
}


// 单个输出流的包装器
typedef struct OutputStream {
	AVStream *st;
	AVCodecContext *enc;

	/* 将要产生的下一帧的播放时间戳 */
	int64_t next_pts;
	int samples_count;

	AVFrame *frame;
	AVFrame *tmp_frame;

	float t, tincr, tincr2;

	struct SwsContext *sws_ctx;
	struct SwrContext *swr_ctx;
} OutputStream;

static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
{
    AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;

    printf("pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
           av_ts2str_f(pkt->pts), av_ts2timestr_f(pkt->pts, time_base),
           av_ts2str_f(pkt->dts), av_ts2timestr_f(pkt->dts, time_base),
           av_ts2str_f(pkt->duration), av_ts2timestr_f(pkt->duration, time_base),
           pkt->stream_index);
}

static int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base, AVStream *st, AVPacket *pkt)
{
    /* 调节输出包的时间基,从编码器时间基到流时间基 */
    av_packet_rescale_ts(pkt, *time_base, st->time_base);
    pkt->stream_index = st->index;

    /* 写入压缩后的帧到输出文件. */
    log_packet(fmt_ctx, pkt);
    return av_interleaved_write_frame(fmt_ctx, pkt);
}
//
///* 添加一个输出流. */
static void add_stream(OutputStream *ost, AVFormatContext *oc,
                       AVCodec **codec,
                       enum AVCodecID codec_id)
{
    AVCodecContext *c;
    int i;

    /* 查找编码器 */
    *codec = avcodec_find_encoder(codec_id);
    if (!(*codec)) {
        fprintf(stderr, "Could not find encoder for '%s'\n",
                avcodec_get_name(codec_id));
        exit(1);
    }

    ost->st = avformat_new_stream(oc, NULL);
    if (!ost->st) {
        fprintf(stderr, "Could not allocate stream\n");
        exit(1);
    }
    ost->st->id = oc->nb_streams-1;
    c = avcodec_alloc_context3(*codec);
    if (!c) {
        fprintf(stderr, "Could not alloc an encoding context\n");
        exit(1);
    }
    ost->enc = c;

    switch ((*codec)->type) {
    case AVMEDIA_TYPE_AUDIO:
        c->sample_fmt  = (*codec)->sample_fmts ?
            (*codec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
        c->bit_rate    = 64000;
        c->sample_rate = 44100;
        if ((*codec)->supported_samplerates) {
            c->sample_rate = (*codec)->supported_samplerates[0];
            for (i = 0; (*codec)->supported_samplerates[i]; i++) {
                if ((*codec)->supported_samplerates[i] == 44100)
                    c->sample_rate = 44100;
            }
        }
        c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);
        c->channel_layout = AV_CH_LAYOUT_STEREO;
        if ((*codec)->channel_layouts) {
            c->channel_layout = (*codec)->channel_layouts[0];
            for (i = 0; (*codec)->channel_layouts[i]; i++) {
                if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO)
                    c->channel_layout = AV_CH_LAYOUT_STEREO;
            }
        }
        c->channels        = av_get_channel_layout_nb_channels(c->channel_layout);
		ost->st->time_base.num = 1;
		ost->st->time_base.den = c->sample_rate;
        break;

    case AVMEDIA_TYPE_VIDEO:
        c->codec_id = codec_id;

        c->bit_rate = 400000;
        /* 设置要合成的视频源的分辨率 */
        c->width    = 1440;
        c->height   = 900;
        /* 设置每一帧的展示时间(时间基). 对于固定帧率,时间基就是帧率的倒数 */
		ost->st->time_base.num = 1;
		ost->st->time_base.den = STREAM_FRAME_RATE;
        c->time_base       = ost->st->time_base;

        c->gop_size      = 12; /*每12帧一个关键帧 */
        c->pix_fmt       = STREAM_PIX_FMT;
        if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
            /* 测试放入B 帧 */
            c->max_b_frames = 2;
        }
        if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
            /* 避免使用宏块.*/
            c->mb_decision = 2;
        }
    break;

    default:
        break;
    }

    /* 一些格式流的头分开放置 */
    if (oc->oformat->flags & AVFMT_GLOBALHEADER)
        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}

/**************************************************************/
/* 音频输出 */

static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt,
                                  uint64_t channel_layout,
                                  int sample_rate, int nb_samples)
{
    AVFrame *frame = av_frame_alloc();
    int ret;

    if (!frame) {
        fprintf(stderr, "Error allocating an audio frame\n");
        exit(1);
    }

    frame->format = sample_fmt;
    frame->channel_layout = channel_layout;
    frame->sample_rate = sample_rate;
    frame->nb_samples = nb_samples;

    if (nb_samples) {
        ret = av_frame_get_buffer(frame, 0);
        if (ret < 0) {
            fprintf(stderr, "Error allocating an audio buffer\n");
            exit(1);
        }
    }

    return frame;
}

static void open_audio(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
{
    AVCodecContext *c;
    int nb_samples;
    int ret;
    AVDictionary *opt = NULL;

    c = ost->enc;

    /* 打开一片编码器 */
    av_dict_copy(&opt, opt_arg, 0);
    ret = avcodec_open2(c, codec, &opt);
    av_dict_free(&opt);
    if (ret < 0) {
        fprintf(stderr, "Could not open audio codec: %s\n", av_err2str_f(ret));
        exit(1);
    }

    /* 初始化信号生成器 */
    ost->t     = 0;
    ost->tincr = 2 * M_PI * 110.0 / c->sample_rate;
    /* 每秒频率增量 */
    ost->tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate;

    if (c->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)
        nb_samples = 10000;
    else
        nb_samples = c->frame_size;

    ost->frame     = alloc_audio_frame(c->sample_fmt, c->channel_layout,
                                       c->sample_rate, nb_samples);
    ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, c->channel_layout,
                                       c->sample_rate, nb_samples);

    /* 拷贝流参数到合成器 */
    ret = avcodec_parameters_from_context(ost->st->codecpar, c);
    if (ret < 0) {
        fprintf(stderr, "Could not copy the stream parameters\n");
        exit(1);
    }

    /* 创建重采样上下文 */
        ost->swr_ctx = swr_alloc();
        if (!ost->swr_ctx) {
            fprintf(stderr, "Could not allocate resampler context\n");
            exit(1);
        }

        /* 设置选项 */
        av_opt_set_int       (ost->swr_ctx, "in_channel_count",   c->channels,       0);
        av_opt_set_int       (ost->swr_ctx, "in_sample_rate",     c->sample_rate,    0);
        av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt",      AV_SAMPLE_FMT_S16, 0);
        av_opt_set_int       (ost->swr_ctx, "out_channel_count",  c->channels,       0);
        av_opt_set_int       (ost->swr_ctx, "out_sample_rate",    c->sample_rate,    0);
        av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt",     c->sample_fmt,     0);

        /* 初始化重采样上下文 */
        if ((ret = swr_init(ost->swr_ctx)) < 0) {
            fprintf(stderr, "Failed to initialize the resampling context\n");
            exit(1);
        }
}

/* 准备一个16位的音频帧,样本*/
static AVFrame *get_audio_frame(OutputStream *ost)
{
    AVFrame *frame = ost->tmp_frame;
    int j, i, v;
    int16_t *q = (int16_t*)frame->data[0];

    /* 检查是否要产生更多的帧 */
	AVRational tempratio;
	tempratio.num = 1;
	tempratio.den = 1;
    if (av_compare_ts(ost->next_pts, ost->enc->time_base,
                      STREAM_DURATION, tempratio) >= 0)
        return NULL;

    for (j = 0; j <frame->nb_samples; j++) {
        v = (int)(sin(ost->t) * 10000);
        for (i = 0; i < ost->enc->channels; i++)
            *q++ = v;
        ost->t     += ost->tincr;
        ost->tincr += ost->tincr2;
    }

    frame->pts = ost->next_pts;
    ost->next_pts  += frame->nb_samples;

    return frame;
}

/*
 * 编码一个音频帧,发送到合成器
 * 编码结束返回1, 其他返回0
 */
static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
{
    AVCodecContext *c;
    AVPacket pkt = { 0 }; // 初始化0;
    AVFrame *frame;
    int ret;
    int got_packet;
    int dst_nb_samples;

    av_init_packet(&pkt);
    c = ost->enc;

    frame = get_audio_frame(ost);

    if (frame) {
        /* 样本从初始格式转换为目标格式, 使用重采样器 */
            /* 计算目标的样本数*/
            dst_nb_samples = av_rescale_rnd(swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples,
                                            c->sample_rate, c->sample_rate, AV_ROUND_UP);
            av_assert0(dst_nb_samples == frame->nb_samples);

        /* 当我们传递一帧给编码器时,可能内部有引用它,此时我们不能写入
         */
        ret = av_frame_make_writable(ost->frame);
        if (ret < 0)
            exit(1);

        /* 转换到目标格式 */
        ret = swr_convert(ost->swr_ctx,
                          ost->frame->data, dst_nb_samples,
                          (const uint8_t **)frame->data, frame->nb_samples);
        if (ret < 0) {
            fprintf(stderr, "Error while converting\n");
            exit(1);
        }
        frame = ost->frame;
		AVRational tempratio;
		tempratio.num = 1;
		tempratio.den = c->sample_rate;
        frame->pts = av_rescale_q(ost->samples_count, tempratio, c->time_base);
        ost->samples_count += dst_nb_samples;
    }

    ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
    if (ret < 0) {
        fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str_f(ret));
        exit(1);
    }

    if (got_packet) {
        ret = write_frame(oc, &c->time_base, ost->st, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error while writing audio frame: %s\n",
                    av_err2str_f(ret));
            exit(1);
        }
    }

    return (frame || got_packet) ? 0 : 1;
}

/**************************************************************/
/* 视频输出 */

static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
{
    AVFrame *picture;
    int ret;

    picture = av_frame_alloc();
    if (!picture)
        return NULL;

    picture->format = pix_fmt;
    picture->width  = width;
    picture->height = height;

    /* 分配帧内存 */
    ret = av_frame_get_buffer(picture, 32);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate frame data.\n");
        exit(1);
    }

    return picture;
}

static void open_video(AVFormatContext *oc, AVCodec *codec, OutputStream *ost, AVDictionary *opt_arg)
{
    int ret;
    AVCodecContext *c = ost->enc;
    AVDictionary *opt = NULL;

    av_dict_copy(&opt, opt_arg, 0);

    /* 打开视频编码器 */
    ret = avcodec_open2(c, codec, &opt);
    av_dict_free(&opt);
    if (ret < 0) {
        fprintf(stderr, "Could not open video codec: %s\n", av_err2str_f(ret));
        exit(1);
    }

    /* 分配可复用的帧 */
    ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
    if (!ost->frame) {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }

    /* 如果输出格式不是YUV420P,那么一个也需要一个临时的YUV420P图片,随后可以把它转换为需要的输出格式 */
    ost->tmp_frame = NULL;
    if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
        ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);
        if (!ost->tmp_frame) {
            fprintf(stderr, "Could not allocate temporary picture\n");
            exit(1);
        }
    }

    /* 拷贝流参数到合成器 */
    ret = avcodec_parameters_from_context(ost->st->codecpar, c);
    if (ret < 0) {
        fprintf(stderr, "Could not copy the stream parameters\n");
        exit(1);
    }
}

/* 准备一帧测试图片. */
static void fill_yuv_image(AVFrame *pict, int frame_index,
                           int width, int height)
{
    int x, y, i;

    i = frame_index;

    /* Y */
    for (y = 0; y < height; y++)
        for (x = 0; x < width; x++)
            pict->data[0][y * pict->linesize[0] + x] = x + y + i * 3;

    /* Cb and Cr */
    for (y = 0; y < height / 2; y++) {
        for (x = 0; x < width / 2; x++) {
            pict->data[1][y * pict->linesize[1] + x] = 128 + y + i * 2;
            pict->data[2][y * pict->linesize[2] + x] = 64 + x + i * 5;
        }
    }
}

static AVFrame *get_video_frame(OutputStream *ost)
{
    AVCodecContext *c = ost->enc;

    /* 检查是否要生成更多的帧 */
	AVRational tempratio;
	tempratio.num = 1;
	tempratio.den = 1;
    if (av_compare_ts(ost->next_pts, c->time_base,
                      STREAM_DURATION, tempratio) >= 0)
        return NULL;

    /* 当我们传递一帧给编码器时,可能内部有引用它,此时我们不能写入 */
    if (av_frame_make_writable(ost->frame) < 0)
        exit(1);

    if (c->pix_fmt != AV_PIX_FMT_YUV420P) {
        /* 如果必要,从YUV420P转换为目标格式*/
        if (!ost->sws_ctx) {
            ost->sws_ctx = sws_getContext(c->width, c->height,
                                          AV_PIX_FMT_YUV420P,
                                          c->width, c->height,
                                          c->pix_fmt,
                                          SCALE_FLAGS, NULL, NULL, NULL);
            if (!ost->sws_ctx) {
                fprintf(stderr,
                        "Could not initialize the conversion context\n");
                exit(1);
            }
        }
        fill_yuv_image(ost->tmp_frame, ost->next_pts, c->width, c->height);
        sws_scale(ost->sws_ctx, (const uint8_t * const *) ost->tmp_frame->data,
                  ost->tmp_frame->linesize, 0, c->height, ost->frame->data,
                  ost->frame->linesize);
    } else {
        fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
    }

    ost->frame->pts = ost->next_pts++;

    return ost->frame;
}

/*
 * 编码视频帧并发送到合成器
 * 编码结束返回1, 其他返回0
 */
static int write_video_frame(AVFormatContext *oc, OutputStream *ost)
{
    int ret;
    AVCodecContext *c;
    AVFrame *frame;
    int got_packet = 0;
    AVPacket pkt = { 0 };

    c = ost->enc;

    frame = get_video_frame(ost);

    av_init_packet(&pkt);

    /* 编码图片 */
    ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
    if (ret < 0) {
        fprintf(stderr, "Error encoding video frame: %s\n", av_err2str_f(ret));
        exit(1);
    }

    if (got_packet) {
        ret = write_frame(oc, &c->time_base, ost->st, &pkt);
    } else {
        ret = 0;
    }

    if (ret < 0) {
        fprintf(stderr, "Error while writing video frame: %s\n", av_err2str_f(ret));
        exit(1);
    }

    return (frame || got_packet) ? 0 : 1;
}

static void close_stream(AVFormatContext *oc, OutputStream *ost)
{
    avcodec_free_context(&ost->enc);
    av_frame_free(&ost->frame);
    av_frame_free(&ost->tmp_frame);
    sws_freeContext(ost->sws_ctx);
    swr_free(&ost->swr_ctx);
}


int _tmain(int argc, _TCHAR* argvec[])  
{  
	OutputStream video_st = { 0 }, audio_st = { 0 };
    const char *filename;
    AVOutputFormat *fmt;
    AVFormatContext *oc;
    AVCodec *audio_codec, *video_codec;
    int ret;
    int have_video = 0, have_audio = 0;
    int encode_video = 0, encode_audio = 0;
    AVDictionary *opt = NULL;

	av_register_all();

    filename = "generated.mp4";


    /* 根据文件名的后缀,分配输出格式的上下文 */
    avformat_alloc_output_context2(&oc, NULL, NULL, filename);
    if (!oc) {
        printf("Could not deduce output format from file extension: using MPEG.\n");
        avformat_alloc_output_context2(&oc, NULL, "mpeg", filename);
    }
    if (!oc)
        return 1;

	//得到输出格式
    fmt = oc->oformat;

    /* 使用默认格式的编码器,增加音频和视频流
     * 初始化编码器 */
    if (fmt->video_codec != AV_CODEC_ID_NONE) {
        add_stream(&video_st, oc, &video_codec, fmt->video_codec);
        have_video = 1;
        encode_video = 1;
    }
    if (fmt->audio_codec != AV_CODEC_ID_NONE) {
        add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec);
        have_audio = 1;
        encode_audio = 1;
    }

    /* 设置所有的参数,打开音频和视频编码器,分配必要的编码内存*/
    if (have_video)
        open_video(oc, video_codec, &video_st, opt);

    if (have_audio)
        open_audio(oc, audio_codec, &audio_st, opt);

	//打印输出流的信息
    av_dump_format(oc, 0, filename, 1);

    /* 打开输出文件 */
    if (!(fmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            fprintf(stderr, "Could not open '%s': %s\n", filename,
                    av_err2str_f(ret));
            return 1;
        }
    }

    /* 写入流的头部 */
    ret = avformat_write_header(oc, &opt);
    if (ret < 0) {
        fprintf(stderr, "Error occurred when opening output file: %s\n",
                av_err2str_f(ret));
        return 1;
    }

    while (encode_video || encode_audio) {
        /* 选择流进行编码 */
        if (encode_video &&
            (!encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base,
                                            audio_st.next_pts, audio_st.enc->time_base) <= 0)) {
            encode_video = !write_video_frame(oc, &video_st);
        } else {
            encode_audio = !write_audio_frame(oc, &audio_st);
        }
    }

    /* 写入流的尾部 */
    av_write_trailer(oc);

    /* 关闭编码器. */
    if (have_video)
        close_stream(oc, &video_st);
    if (have_audio)
        close_stream(oc, &audio_st);

    if (!(fmt->flags & AVFMT_NOFILE))
        /* 关闭输出文件*/
        avio_closep(&oc->pb);

    /* 释放编码上下文 */
    avformat_free_context(oc);
	system("PAUSE");
	return 0;  
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Ffmpeg视频开发教程(七)——基于ffmpeg4.0生成模拟yuv数据和模拟音频数据再合成为mp4文件 的相关文章

  • 音视频开发--音视频基础

    音视频基础 一 音视频录制原理 视频录制流程 1 准备摄像头 2 图像帧阶段 从摄像头采集视频数据 xff08 图像帧 xff09 xff0c 采集数据格式 xff1a YUV或者RGB xff0c YUV和RGB细分的话还包括YUV 4
  • FFmpeg音频处理——音频混合、拼接、剪切、转码

    接触FFmpeg有一段时间了 它是音视频开发的开源库 几乎其他所有播放器 直播平台都基于FFmpeg进行二次开发 本篇文章来总结下采用FFmpeg进行音频处理 音频混合 音频剪切 音频拼接与音频转码 采用android studio进行开发
  • 企业级音视频会议实战之webrtc服务器janus品尝实战

    1 前言 之前写过单纯用webrtc和springboot实现单人 多人 屏幕分享等功能的一系列文章了 心疼各位giegie 已将例子放在下面 不麻烦你们去找了 webrtc实现视频群聊系列文章 一 之基础入门 webrtc实现视频群聊系列
  • 深入剖析阻塞式socket的timeout

    1 前言 网络编程中超时时间是一个重要但又容易被忽略的问题 对其的设置需要仔细斟酌 本文讨论的是socket设置为阻塞模式 如果socket处于阻塞模式运行时 就需要考虑处理socket操作超时的问题 所谓阻塞模式 是指其完成指定的操作之前
  • Opengl ES之水印贴图

    前言 水印贴图又称画中画 这种功能在Opengl中是如何实现的呢 我们可以简单地理解成两张纹理的叠加 一个纹理作为背景 另外一个纹理通过调整顶点坐标作为一个小的前景 说到水印贴图的实现 很多朋友可能会想到通过mix混合函数实现 但是并不推荐
  • FFmpeg源码分析:av_register_all()注册封装器与解封装器

    FFmpeg中av register all 函数用于注册所有muxers demuxers与protocols FFmpeg4 0以前是用链表存储muxer demuxer FFmpeg4 0以后改为数组存储 并且av register
  • Ubuntu22.04编译安装FFmpeg

    FFmpeg介绍 概述 FFmpeg是一款用C语言编写的跨平台免费开源多媒体处理工具 该软件可实现音视频的采集 编解码 转码 过滤以及流媒体相关操作等功能 同时 FFmpeg也为其他多种语言和操作系统提供了开发组件 包括Java Pytho
  • 流媒体技术基础-流媒体编码与协议

    一 流媒体传输协议 1 实时传输协议RTP 针对多媒体数据流的一种传输协议 建立在UDP协议上 属于传输层协议 定义互联网上传递音频和视频的标准数据包格式 RTP协议常用于流媒体系统 配合RTCP协议 视频会议和视频电话系统 配合H 263
  • FFmpeg从入门到入魔(2):保存流到本地MP4

    1 FFmpeg裁剪移植 之前我们简单地讲解了下如何在Linux系统中编译FFmpeg 但是编译出来的so体积太大 而且得到的多个so不便于使用 本节在此基础上 将详细讲解在编译FFmpeg时如何对相关模块作裁剪以精简so的体积 并且编译只
  • Python音视频开发:消除抖音短视频Logo的图形化工具实现过程详解

    前往老猿Python博文目录 一 引言 在 Python音视频开发 消除抖音短视频Logo和去电视台标的实现详解 节介绍了怎么通过Python Moviepy OpenCV实现消除视频Logo的四种方法 并提供了详细的实现思路和实现代码 但
  • 浏览器播放rtsp视频流:2、ffmpeg转hls播放(go后端利用hls做简单视频直播)

    浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 文章目录 浏览器播放rtsp视频流 2 ffmpeg转hls播放 go后端利用hls做简单视频直播 1 前言 2 wsl安装ffmpeg并转换rtsp
  • Nginx+FFmpeg实现rtsp流转hls流,在WEB通过H5 video实现视频播放

    概述 本文介绍通过Nginx FFmpeg实现rtsp流转hls流 在WEB通过H5 video标签实现视频播放功能 此方法可用于网络摄像头RTSP视频流WEB端实时播放 一 FFmpeg Nginx转流hls 1 FFmpeg安装 官网
  • Android平台RTSP

    1 背景 我们在做Android平台RTSP或者RTMP播放器开发的时候 需要注意的点非常多 以下 以大牛直播SDK 官方 的接口为例 大概介绍下相关接口设计 2 接口设计 2 1 Open 接口 Open接口的目的 主要是创建实例 正常返
  • 音视频开发(40)---麦克风阵列声源定位 GCC-PHAT

    麦克风阵列声源定位 GCC PHAT 版权声明 本文为博主原创文章 未经博主允许不得转载 https blog csdn net u010592995 article details 79735198 麦克风阵列声源定位 一 利用麦克风阵列
  • 《WebRTC系列》实战 Web 端支持 h265 硬解

    1 背景 Web 端实时预览 H 265 需求一直存在 但由于之前 Chrome 本身不支持 H 265 硬解 软解性能消耗大 仅能支持一路播放 该需求被搁置 去年 9 月份 Chrome 发布 M106 版本 默认开启 H 265 硬解
  • 视频编解码(一):ffmpeg编码H.264帧类型判断

    本文主要讲述ffmpeg编码过程中是如何设置I帧 B帧及P帧的 以及如何通过代码判断帧类型 之前看过很多网上的文章 讲述如何判断I帧 B帧 P帧 然而都是停留在H 264官方文档中的定义 如果不结合ffmpeg 就仿佛纸上谈兵 有点不切实际
  • IJKPlayer 编译及运行Demo 教程(详细)

    IJKPlayer 编译及运行Demo 教程 编译环境准备 1 Ubuntu安装 2 工具安装 安装JDK SDK NDK 1 安装JDK 2 安装SDK 3 安装NDK 下载ijkplayer及修改其中配置 编译 android stud
  • moviepy音视频剪辑:视频半自动追踪人脸打马赛克

    一 引言 在 moviepy1 03音视频剪辑 使用manual tracking和headblur实现追踪人脸打马赛克 介绍了使用手动跟踪跟踪人脸移动轨迹和使用headblur对人脸进行打马赛克 实际上 moviepy除了使用manual
  • 浏览器播放rtsp视频流:3、rtsp转webrtc播放

    浏览器播放rtsp视频流 3 rtsp转webrtc播放 文章目录 浏览器播放rtsp视频流 3 rtsp转webrtc播放 1 前言 2 rtsp转webRTC 3 初步测试结果 4 结合我们之前的onvif gSoap cgo的方案做修
  • FFmpeg转码流程和常见概念

    视频格式 mkv flv mov wmv avi mp4 m3u8 ts等等 FFmpeg的转码工具 它的处理流程是这样的 从输入源获得原始的音视频数据 解封装得到压缩封装的音视频包 对音视频包进行解码 得到原始的音视频帧 对原始音视频帧进

随机推荐

  • Jenkins自动化测试

    学习 Jenkins 自动化测试的系列文章 Robot Framework 概念 Robot Framework 安装 Pycharm Robot Framework 环境搭建 Robot Framework 介绍 Jenkins 自动化测
  • mariadb主从复制

    1 准备两台服务器 一台设置为master服务器 一台设置为slave服务器 两台服务器先关闭防火墙 关闭selinux systemctl stop firewalld setenforce 0 两台服务器都需要安装mariadb 这里用
  • google c++代码规范-中文翻译

    头文件 lt Header Files gt The define Guard 所有的头文件都应该使用 define等来防止头文件重复包含 宏的格式应该为
  • Python实现二叉搜索树的删除功能

    Python实现二叉搜索树的删除功能 二叉搜索树 二叉查找树 Binary Search Tree 又称为排序二叉树 有序二叉树 二叉搜索树的实现可以参考 https blog csdn net weixin 43790276 articl
  • 标准正态分布变量的积累概率分布函数C\C++

    BS模型中用到的CDF函数实现 找到两种常见的实现方式 实现一 include
  • 图像处理——我理解的傅里叶变换

    1 傅里叶变换的理解 傅里叶变换的相关数学公式目前还没有搞懂 先不整那个东西 我们主要是研究傅里叶变换的一些思想和应用 这个思想起源于牛顿研究那个三棱镜 白光透过棱镜之后会被分解为七种颜色的光 这些光叠加又能形成白光 所以说可以把一种事物分
  • selenium自动向下滚动页面,并指定最大滑动距离

    需要selenium控制的chrome向下滑动 自动加载一些内容 核心代码是 browser execute script window scrollBy 0 300 这行可以向下滑动300个像素 需要的工具函数如下 def roll wi
  • unity实现鼠标右键控制视角

    主要实现的功能是相机跟随主角 鼠标右击移动后 相机的视角会旋转 思路 在主角里创建空的子物体 把相机绑在空物体上 通过旋转空物体来实现视角的旋转 要把相机调整到适当位置 代码如下 public float rotateSpeed 100 设
  • chatgpt赋能python:Python打包发布完整指南:从基础知识到实践操作

    Python打包发布完整指南 从基础知识到实践操作 作为一名有着十年python编程经验的工程师 我清楚地知道打包发布Python应用程序是非常重要的 它能帮助我们方便地分享和分发程序 并且能够让其他人通过使用我们的程序来提高自己的工作效率
  • 别人总结的一些git教程大全

    工作中 除了必备的基础知识 还要学会与人合作 如何将你开发的小功能整合到整个项目的大框架中 如何让你的实验性代码不影响到大框架中的代码性能 如何让你放下手中写到一半的代码去修改突然出现的bug 这些都是会出现的情况 为了应对这些情况 新入职
  • Qt QML多线程-WorkerScript的使用

    Qt QML多线程 WorkerScript的使用 在开发过程中 常常会遇到一些需要进行耗时计算的操作 如果这些操作都放在主线程中完成 就会导致UI界面被卡死 用户体验很不好 为了解决这个问题 我们可以将这些耗时计算操作放在一个单独的线程中
  • java综合(六)hibernate.current_session_context_class配置

    在前面一节 spring与Hibernate整合 事务 中 总是出现不存在激活事务的问题 结果去掉
  • 使用命令启动默认程序(例如启动系统默认浏览器打开指定网址)

    文章目录 目的 基础说明 代码示例 Golang 总结 目的 通过命令调用系统默认应用程序打开对应格式的文件是比较常用的功能 这篇文章将介绍下相关内容 基础说明 Windows windows下可以使用 start 指令来启动默认程序打开对
  • 数据结构——广度优先遍历(BFS)无向连通图

    以下是数据结构中关于广度优先遍历无向连通图的操作 编程风格参考严蔚敏版数据结构 其实深度优先遍历就是二叉树的层次遍历的推广 头文件及宏 include
  • Python----利用Threading和Queue实现多线程

    用来学习Threading Queue的组合使用 实现多线程编程 实现功能 利用 ping 来检测主机是否存活 代码如下 coding utf 8 from IPy import IP from subprocess import Pope
  • 2022年 大学生工程训练比赛[物料搬运]

    本人和团结参加了2022年大学生工程训练 简称工训赛 校赛选拔 准备了几个月的时间和花费了较多的资金 由于疫情等多种情况 很遗憾未能参加湖南省省赛 过了这么久还是写个博客记录参赛准备和调试过程 目录 一 比赛要求 二 整体思路 三 硬件模块
  • 所有OLE接口

    比较有用 记录下来供查阅 常规 函数 lUnknown 目的 控制的接口协商的对象生存期 普遍存在的任何组件 而不考虑实现 QueryInterface 公开传入的接口 函数 IEnum 目的 枚举的各种类型的列表 在许多情况下 整个 OL
  • 计算机扫描的文件保存在哪,电脑教程:文件扫描后自动保存哪里去了

    科技本身 支配宇宙的自然规律是充满魅力的 因此越来越多的人开始关注科技的相关动态 近来文件扫描后自动保存哪里去了的消息也是引起了很多人的关注 那么既然现在大家都想要知道文件扫描后自动保存哪里去了 小编今天就来给大家针对文件扫描后自动保存哪里
  • 关于 运算符号 &(与运算)、

    1 与运算 在二进制中 运算规则 0 0 0 0 1 0 1 0 0 1 1 1 类比到十进制 例如 3和4 首先化成二进制 就是 011 和 100 再进行相同位上的与运算 就是 000 最后就是0 因为是 运算符号 所以返回的是int
  • Ffmpeg视频开发教程(七)——基于ffmpeg4.0生成模拟yuv数据和模拟音频数据再合成为mp4文件

    本文主要实现使用最新版的ffmpeg生成模拟yuv数据和模拟音频数据再合成为mp4文件 重要代码都是来自官方 稳定性可靠 本文程序的环境搭建参考我的第一篇FFMPEG教程 https blog csdn net zhangamxqun ar