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

这是运行时的截图,这边好像现实aac和h264在avcodec_receive_packet
操作的时候遇到了一些问题,这片文章来分析解决一下,刚好来探讨一下有关time_base
的问题。
从debug看time_base
首先我们找到打印apts
和vpts
的地方:

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

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

这里的时间单位其实需要看时间基来获得,这里需要我们debug一下音视频流的时间基问题。
视频流时间基
我们首先在视频流这里打断点,查看调试信息,在41行执行之前,时间基为{0,1}
,接下来在41行我们会认为制定时间基。

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

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

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


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

这个时间基其实是在外面创建流的时候ffmpeg根据上下文创建的,给的是90kHz
,大家注意这个值不要去修改。
音频时间基
接下来我们来跟踪一下音频时间基,依然是打一些断点。

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

这边AVStream
中的时间基和之前的一样,大家注意这里的时间基千万不要修改,这是FFmpeg帮我们设置的。
关于是否要初始化时间基
细心的朋友可能发现了,在初始化音频和视频的时候没有都初始化时间基。


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

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

这边会直接报错,说明还是需要我们初始化的。
解决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();
}
}
评论