小工具知识网

当前位置: 首页 > 行业知识 > 音视频学习笔记——实现PCM和H264合成MP4功能

行业知识

音视频学习笔记——实现PCM和H264合成MP4功能

2026-11-12 19:08:09 来源:互联网转载

���文主要记录实现PCM和H264合成MP4功能的整个框架,各个模块中FFmpeg的api使用流程,便于后续学习和复盘。


本专栏知识点是通过的音视频流媒体高级开发课程进行系统学习,梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育


导航小助手

    • 1. MP4合成
    • 2. muxer类
    • 3. audioencoder类
    • 4. videoencoder类
    • 5. audioresampler类
    • 6. mian函数:

      1. MP4合成

      MP4合成包括音频和视频以及封装器3部分,框架如下图所示。

      2. muxer类

      首先,在h.文件中声明相关函数和参数。

      以下是各个函数中重要的api使用。

      Init():初始化

      //初始化一个用于输出的AVFormatContext结构体。其声明位于libavformat\avformat.h,avformat_alloc_output_context2(&fmt_ctx_,NULL,NULL,url)//

      DeInit():资源释放

      //关闭打开的流avformat_close_input(&fmt_ctx_); 

      *AddStream(AVCodecContext codec_ctx):创建流

      //创建流AVStream *st = avformat_new_stream(fmt_ctx_,NULL);//从编码器上下文复制    avcodec_parameters_from_context(st->codecpar, codec_ctx);    //打印输入流     av_dump_format(fmt_ctx_, 0, url_.c_str(), 1);

      SendHeader():写header文件

      //分配一个 stream 的私有数据而且写 stream 的 header 到一个输出的媒体文件。int ret = avformat_write_header(fmt_ctx_, NULL); 

      SendPacket():写packet,与

       AVRational src_time_base; //编码后的包    AVRational dst_time_vase; //mp4输出文件对应流的time_base     //时间基转换    packet->pts = av_rescale_q(packet->pts,src_time_base,dst_time_vase);    packet->dts = av_rescale_q(packet->dts,src_time_base,dst_time_vase);    packet->duration = av_rescale_q(packet->duration,src_time_base,dst_time_vase);        ret = av_interleaved_write_frame(fmt_ctx_,packet); //不是立即写入文件,内部缓存,主要是对pts进行排序    //ret = av_write_frame(fmt_ctx_,packet);

      SendTrailer():输出文件尾

      //用于输出文件尾av_write_trailer(fmt_ctx_);

      在对音频和视频进行编码得到数据流后,用muxer类实现将音视频流编码成mp4格式。

      3. audioencoder类

      在h.文件中声明相关函数和参数。

      主要函数实现:

      1.初始化AAC:InitAAC(int channels, int sample_rate, int bit_rate);2.编码:*Encode(AVFrame *frame, int stream_index, int64_t pts, int64_t time_base);3.返回一些常用的参数int GetFrameSize(); //获取一帧数据,每个通道需要多少个采样点    int GetSampleFormat();  //编码器需要的采样格式    int GetChannels(); //获取通道数    int GetSampleRate(); //获取采样率    AVCodecContext *GetCodecContext();

      InitAAC():初始化

      参数:

      • pcm_channels:pcm通道数
      • pcm_sample_rate:pcm样本采样率
      • audio_bit_rate:音频比特率
        //1.avcodec_find_encoder() 用于查找 FFmpeg 的编码器,AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_AAC);//获取的是默认的AAC。//2.avcodec_alloc_context3()主要是创建了 AVCodecContext ,并给结构体参数赋予初值。//初值设置主要分成两块,1. 所有编码器都相同的部分;2.每个编码器独有的参数设置。codec_ctx_ = avcodec_alloc_context3(codec);//配置参数codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //编码后的aac文件不会带ADTS Header    codec_ctx_->bit_rate = bit_rate_;    codec_ctx_->sample_rate = sample_rate_;    codec_ctx_->sample_fmt = AV_SAMPLE_FMT_FLTP;    codec_ctx_->channels = channels_;    codec_ctx_->channel_layout = av_get_default_channel_layout(codec_ctx_->channels);    //3.初始化一个视音频编解码器的 AVCodecContext    int ret = avcodec_open2(codec_ctx_, NULL, NULL);

        Encode():编码

        参数:

        • frame:帧
        • stream_index:数据流序号
        • pts:显示时间
        • time_base:时间基
          //时间转换frame->pts = av_rescale_q(pts,AVRational{1, (int)time_base}, codec_ctx_->time_base);//1.avcodec_send_frame()首先判断编码器有没打开、是否为编码器。//发送AVFrameint ret = avcodec_send_frame(codec_ctx_,frame);//av_packet_alloc(),申请的AVPacket*AVPacket *packet = av_packet_alloc();//接受packet    ret = avcodec_receive_packet(codec_ctx_,packet);

          4. videoencoder类

          实现与audioencoder类相似,但细节处不同。

          1.初始化H264:int InitH264(int width, int height, int fps, int bit_rate);2.编码:AVPacket *Encode(uint8_t *yuv_data, int yuv_size,                     int stream_index, int64_t pts, int64_t time_base);

          InitH264():

          • width_ :画面宽度
          • height_ :画面高度
          • fps_ :帧率
          • bit_rate_ :比特率
            //1.avcodec_find_encoder() 用于查找 FFmpeg 的编码器,AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);//2.avcodec_alloc_context3()主要是创建了 AVCodecContext ,并给结构体参数赋予初值。//初值设置主要分成两块,1. 所有编码器都相同的部分;2.每个编码器独有的参数设置。codec_ctx_ = avcodec_alloc_context3(codec);//配置参数codec_ctx_->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; //编码后的aac文件不会带ADTS Header    codec_ctx_->bit_rate = bit_rate_;    codec_ctx_->width = width_;    codec_ctx_->height = height_;    codec_ctx_->framerate = {fps, 1};    codec_ctx_->time_base = {1,1000000}; //单位为微妙    codec_ctx_->gop_size =fps_;    codec_ctx_->max_b_frames =0; //B帧数量    codec_ctx_->pix_fmt = AV_PIX_FMT_YUV420P;    //3.初始化一个视音频编解码器的 AVCodecContext    int ret = avcodec_open2(codec_ctx_, NULL, NULL);     frame_ =av_frame_alloc();//视频与音频实现不同之处,需要声明下frame帧

            Encode():

            参数:

            • yuv_data:存放yuv帧的buffer
            • yuv_size:yuv帧的大小
            • stream_index:数据流序号
            • pts:显示时间
            • time_base:时间基
              //时间转换frame->pts = av_rescale_q(pts,AVRational{1, (int)time_base}, codec_ctx_->time_base);//不同之处,将yuv填充成需要的格式int ret_size = av_image_fill_arrays(frame_->data, frame_->linesize,                             yuv_data, (AVPixelFormat)frame_->format,                             frame_->width,frame_->height,1);int ret = avcodec_send_frame(codec_ctx_,frame);AVPacket *packet = av_packet_alloc();    ret = avcodec_receive_packet(codec_ctx_,packet); 

              5. audioresampler类

              1.初始化S16转FLTP:int InitFormS16ToFLTP(int in_channles,int in_sample_rate, int out_channles, int out_sample_rate);2.重采样:int ResampleFormS16ToFLTP(uint8_t *in_data, AVFrame *out_frame);

              InitFormS16ToFLTP():

              参数:

              • in_channles_:输入通道数
              • in_sample_rate_:输入采样率
              • out_channles_:输出通道数
              • out_sample_rate_:输出采样率
                //重采样参数设置ctx_ = swr_alloc_set_opts(ctx_,                              av_get_default_channel_layout(out_channles_),                              AV_SAMPLE_FMT_FLTP,                              out_sample_rate_,                              av_get_default_channel_layout(in_channles_),                              AV_SAMPLE_FMT_S16,                              in_sample_rate_,                              0,                              NULL);//初始化一个重采样                            int ret = swr_init(ctx_);

                ResampleFormS16ToFLTP():

                参数:

                • in_data: pcm帧buffer
                • out_fream: fktp帧
                  int AudioResampler::ResampleFormS16ToFLTP(uint8_t *in_data, AVFrame *out_frame){    const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};    indata[0] = in_data;    //进行转换    int samples = swr_convert(ctx_, out_frame->data, out_frame->nb_samples,                          indata,out_frame->nb_samples);}

                  6. mian函数:

                  实现流程:

                  6.1 打开yuv、pcm文件

                  // 打开YUV文件n_yuv_fd = fopen(in_yuv_name, "rb");// 打开PCM文件    in_pcm_fd = fopen(in_pcm_name, "rb");

                  6.2 初始化编码器,包括视频、音频编码器,分配yuv、pcm的帧buffer

                  //2.1 初始化video    //初始化编码器    video_encoder.InitH264(yuv_width, yuv_height, yuv_fps, video_bit_rate);    //分配 yuv buf    int y_frame_size = yuv_width * yuv_height;    int u_frame_size = yuv_width * yuv_height / 4;    int v_frame_size = yuv_width * yuv_height / 4;    int yuv_frame_size = y_frame_size + u_frame_size + v_frame_size;    uint8_t *yuv_frame_buf = (uint8_t *)malloc(yuv_frame_size);//2.2 初始化 audio    //初始化音频编码器    audio_encoder.InitAAC(pcm_channels,pcm_sample_rate, audio_bit_rate);    //分配pcm buf    // pcm_frame_size = 单个字节点占用的字节 * 通道数量 * 每个通道有多少给采样点    int pcm_frame_size = av_get_bytes_per_sample((AVSampleFormat)pcm_sample_format)            *pcm_channels * audio_encoder.GetFrameSize();    uint8_t *pcm_frame_buf = (uint8_t *)malloc(pcm_frame_size);//2.3 初始化重采样AudioResampler audio_resampler;    audio_resampler.InitFormS16ToFLTP(pcm_channels, pcm_sample_rate,                                      audio_encoder.GetChannels(),audio_encoder.GetSampleRate());

                  6.3 mp4初始化,包括新建流,open io, send header

                      Muxer mp4_muxer;    mp4_muxer.Init(out_mp4_name);    //创建视频流、音频流    mp4_muxer.AddStream(video_encoder.GetCodecContext());    mp4_muxer.AddStream(audio_encoder.GetCodecContext());    mp4_muxer.Open();    mp4_muxer.SendHeader();

                  6.4.在while循环读取yuv、pcm进行编码然后发送给mp4 muxer

                  //1. 时间戳相关    int64_t audio_time_base = AUDIO_TIME_BASE;    int64_t video_time_base = VIDEO_TIME_BASE;    double audio_frame_duration = 1.0 * audio_encoder.GetFrameSize()/pcm_sample_rate *audio_time_base;    double video_frame_duration = 1.0 / yuv_fps * video_time_base;while(1){        if(audio_finish && video_finish){            break;        }        printf("apts:%0.0lf,vpts:%0.0lf\n",audio_pts/1000,video_pts/1000);        if(video_finish != 1 && audio_pts > video_pts //audio和video都还有数据,优先audio(audio_pts > video_pts)                || (video_finish != 1 && audio_finish == 1)){            read_len = fread(yuv_frame_buf, 1,yuv_frame_size,in_yuv_fd);            if(read_len = 0){                for(int i = 0; i                    ret = mp4_muxer.SendPacket(packets[i]);                }            }            packets.clear();        }else if(audio_finish != 1){            read_len = fread(pcm_frame_buf, 1, pcm_frame_size, in_pcm_fd);            if(read_len time_base = {1,1000000}; //单位为微妙

                  2.有很大延迟,需要设置0延迟,进行如下修改。

                  h.定义AVDictionary *dict_ =NULL;cpp修改av_dict_set(&dict_,"tune","zerolatency", 0);    int ret = avcodec_open2(codec_ctx_, NULL, dict_);//释放内存if(dict_){        av_dict_free(&dict_);    }

上一篇:前端首屏优化 | 借助客户端能力提升 H5 首屏的 8 个手段

下一篇:怎么看服务器真实带宽?