上次已经写完了如何使用FFmpeg来合成mp4,但是在使用的过程中遇到了一些问题:

![image-20250714000545375](/Users/lenn/Library/Application Support/typora-user-images/image-20250714000545375.png)

这是运行时的截图,这边好像现实aac和h264在avcodec_receive_packet操作的时候遇到了一些问题,这片文章来分析解决一下,刚好来探讨一下有关time_base的问题。

从debug看time_base

首先我们找到打印aptsvpts的地方:

![image-20250714001344849](/Users/lenn/Library/Application Support/typora-user-images/image-20250714001344849.png)

其实是在这个地方,我们并没有打印每个packetpts值,我们这里来尝试打印一下,在下面的位置添加一行代码:

![image-20250714001846167](/Users/lenn/Library/Application Support/typora-user-images/image-20250714001846167.png)

这边打印的结果是,在音频流打印到74微妙后,才有了第一次的视频流打印:

![image-20250714002230777](/Users/lenn/Library/Application Support/typora-user-images/image-20250714002230777.png)

这里的时间单位其实需要看时间基来获得,这里需要我们debug一下音视频流的时间基问题。

视频流时间基

我们首先在视频流这里打断点,查看调试信息,在41行执行之前,时间基为{0,1},接下来在41行我们会认为制定时间基。

![image-20250714092102555](/Users/lenn/Library/Application Support/typora-user-images/image-20250714092102555.png)

我们让程序继续执行之后发现,时间基变成了我们人为指定的:

![image-20250714092302015](/Users/lenn/Library/Application Support/typora-user-images/image-20250714092302015.png)

然后我们单步执行,现在程序在47行,我们执行47行之后看看结果,结果还是我们人工指定的时间基。

![image-20250714092656208](/Users/lenn/Library/Application Support/typora-user-images/image-20250714092656208.png)

接下来我们看我们封装器中的结果,也就是下面这一行:

![image-20250714092752368](/Users/lenn/Library/Application Support/typora-user-images/image-20250714092752368.png)

![image-20250714093445353](/Users/lenn/Library/Application Support/typora-user-images/image-20250714093445353.png)

根据调试信息我们可以看出来这里的video_stream的时间基似乎和我们设置的不太一样。那这里的时间基是哪里来的呢,那就要看这个流是哪里来的。

![image-20250714093556535](/Users/lenn/Library/Application Support/typora-user-images/image-20250714093556535.png)

这个时间基其实是在外面创建流的时候ffmpeg根据上下文创建的,给的是90kHz,大家注意这个值不要去修改。

音频时间基

接下来我们来跟踪一下音频时间基,依然是打一些断点。

![image-20250714095311363](/Users/lenn/Library/Application Support/typora-user-images/image-20250714095311363.png)

这边的断点显示在我们打开上下文后,我们的时间基被初始化了为{1,44100}也就是我们需要44100个采样点才能组成一帧数据。

![image-20250714095558584](/Users/lenn/Library/Application Support/typora-user-images/image-20250714095558584.png)

这边AVStream中的时间基和之前的一样,大家注意这里的时间基千万不要修改,这是FFmpeg帮我们设置的。

关于是否要初始化时间基

细心的朋友可能发现了,在初始化音频和视频的时候没有都初始化时间基。

![image-20250714095806598](/Users/lenn/Library/Application Support/typora-user-images/image-20250714095806598.png)

![image-20250714095820272](/Users/lenn/Library/Application Support/typora-user-images/image-20250714095820272.png)

那么我们将这边视频的时间基去掉看看情况:

![image-20250714100003519](/Users/lenn/Library/Application Support/typora-user-images/image-20250714100003519.png)

可以看到我们的视频avcodec_open的时候是不会帮我们初始化时间基的,那我们直接运行一下程序看看情况:

![image-20250714100107982](/Users/lenn/Library/Application Support/typora-user-images/image-20250714100107982.png)

这边会直接报错,说明还是需要我们初始化的。

解决bug

这里的问题主要是我们在avcodec_receive_packet的地方没有完全接收所有packet。这里我们添加一些逻辑就好了。

修改后的几个部分如下:

int AudioEncoder::Encode(AVFrame *frame, int stream_index, int64_t pts, int64_t time_base, std::vector<AVPacket*> &packets)
{
    if(!codec_ctx_) {
        printf("codec_ctx_ null\n");
        return -1;
    }
    pts = av_rescale_q(pts, AVRational{1, (int)time_base}, codec_ctx_->time_base);
    if(frame) {
        frame->pts = pts;
    }
    int ret = avcodec_send_frame(codec_ctx_, frame);
    if(ret != 0) {
        char errbuf[1024] = {0};
        av_strerror(ret, errbuf, sizeof(errbuf) - 1);
        printf("avcodec_send_frame failed:%s\n", errbuf);
        return -1;
    }

    while (1)
    {
        /* code */
        AVPacket *packet = av_packet_alloc();
        
        ret = avcodec_receive_packet(codec_ctx_, packet);
      // 这里一定要注意先接收再设置index,不要alloc后就设置
        packet->stream_index = stream_index;

        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            ret = 0;
            av_packet_free(&packet);
            break;
        }
        if(ret != 0) {
            char errbuf[1024] = {0};
            av_strerror(ret, errbuf, sizeof(errbuf) - 1);
            printf("aac avcodec_receive_packet failed:%s\n", errbuf);
            av_packet_free(&packet);
            ret = -1;
        }
        packets.push_back(packet);
    }
    
    
    return ret;
}
int VideoEncoder::Encode(uint8_t *yuv_data, int yuv_size, int stream_index, int64_t pts, int64_t time_base, std::vector<AVPacket*> &packets)
{
    if(!codec_ctx_) {
        printf("codec_ctx_ null\n");
        return -1;
    }
    int ret = 0;

    pts = av_rescale_q(pts, AVRational{1, (int)time_base}, codec_ctx_->time_base);
    frame_->pts = pts;
    if(yuv_data) {
        int ret_size = av_image_fill_arrays(frame_->data, frame_->linesize,
                                            yuv_data, (AVPixelFormat)frame_->format,
                                            frame_->width, frame_->height, 1);
        if(ret_size != yuv_size) {
            printf("ret_size:%d != yuv_size:%d -> failed\n", ret_size, yuv_size);
            return -1;
        }
        ret = avcodec_send_frame(codec_ctx_, frame_);
    } else {
        ret = avcodec_send_frame(codec_ctx_, NULL);
    }

    if(ret != 0) {
        char errbuf[1024] = {0};
        av_strerror(ret, errbuf, sizeof(errbuf) - 1);
        printf("avcodec_send_frame failed:%s\n", errbuf);
        return -1;
    }

    while (1)
    {
        /* code */
        AVPacket *packet = av_packet_alloc();
        ret = avcodec_receive_packet(codec_ctx_, packet);
        packet->stream_index = stream_index;
        
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            ret = 0;
            av_packet_free(&packet);
            break;
        }
        else if(ret < 0) {
            char errbuf[1024] = {0};
            av_strerror(ret, errbuf, sizeof(errbuf) - 1);
            printf("h264 avcodec_receive_packet failed:%s\n", errbuf);
            av_packet_free(&packet);
            ret = -1;
        }
        
        packets.push_back(packet);
    }
    
    
    return ret;
}
//这是main函数的主循环
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和vidoe都还有数据,优先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 < yuv_frame_size) {
                video_finish = 1;
                printf("fread yuv_frame_buf finish\n");
            }

            packets.clear();

            if(video_finish != 1) {
                ret = video_encoder.Encode(yuv_frame_buf, yuv_frame_size, video_index,
                                              video_pts, video_time_base, packets);
            }else {
                ret = video_encoder.Encode(NULL, 0, video_index,
                                              video_pts, video_time_base, packets);
            }
            video_pts += video_frame_duration;  // 叠加pts
            
            if (ret >= 0) {
                for (int i = 0; i < packets.size(); 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 < pcm_frame_size) {
                audio_finish = 1;
                printf("fread pcm_frame_buf finish\n");
            }

            if(audio_finish != 1) {
                AVFrame *fltp_frame = AllocFltpPcmFrame(pcm_channels, audio_encoder.GetFrameSize());
                ret = audio_resampler.ResampleFromS16ToFLTP(pcm_frame_buf, fltp_frame);
                packets.clear();
                if(ret < 0)
                    printf("ResampleFromS16ToFLTP error\n");
                ret = audio_encoder.Encode(fltp_frame, audio_index,
                                              audio_pts, audio_time_base, packets);
                FreePcmFrame(fltp_frame);
            }else {
                ret = audio_encoder.Encode(NULL,audio_index,
                                              audio_pts, audio_time_base, packets);
            }
            audio_pts += audio_frame_duration;  // 叠加pts
            if (ret >= 0) {
                for (int i = 0; i < packets.size(); i++) {
                    ret = mp4_muxer.SendPacket(packets[i]);
                }
            }
            packets.clear();
        }
    }