Note: I have a working example of the problem here.
I'm using the libav/ffmpeg API to generate an MP4 with the h264 codec. In my specific situation I'm generating the files with a max number of 2 "B" frames. I'm able to generate an Mp4 with the right number of frames so that a single, lone "B" frame is the very last frame being written. When this happens, the encoder sets that frame's packet to be discarded (I've verified this with ffprobe). The net result is that some players (say, when dropping the MP4 into Edge or Chrome) will display only n-1 total frames (ignoring the discarded packet). Other players, such as VLC, will play the full n frames (not ignoring the discarded packet). So, the result is inconsistent.
ffmpeg.exe itself doesn't appear to have this problem. Instead, it will set what would be the lone "B" frame to a "P" frame. This means the file will play the same regardless of what player is used.
The problem is: I don't know how to mimic ffmpeg's behavior using the SDK so the last frame will play regardless of the player. As far as I can tell I'm closing out the file properly by flushing out the encoder buffers. I must be doing something wrong somewhere.
I provided a link to the full source above, but at a high level I'm initializing the codec context and stream like this:
newStream->codecpar->codec_id = AV_CODEC_ID_H264;
newStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
newStream->codecpar->width = Width;
newStream->codecpar->height = Height;
newStream->codecpar->format = AV_PIX_FMT_YUV420P;
newStream->time_base = { 1, 75 };
avcodec_parameters_to_context(codecContext, newStream->codecpar);
codecContext->time_base = { 1, 75 };
codecContext->gop_size = 30;
I then sit in a loop and use OpenCV to generate frames (each frame has its frame number drawn on it):
auto matrix = cv::Mat(Height, Width, CV_8UC3, cv::Scalar(0, 0, 0));
std::stringstream ss;
ss << f;
cv::putText(matrix, ss.str().c_str(), textOrg, fontFace, fontScale, cv::Scalar(255, 255, 255), thickness, 8);
I then write out the frame like this (looping if more data is needed):
if ((ret = avcodec_send_frame(codecContext, frame)) == 0) {
ret = avcodec_receive_packet(codecContext, &pkt);
if (ret == AVERROR(EAGAIN))
{
continue;
}
else
{
av_interleaved_write_frame(pFormat, &pkt);
}
av_packet_unref(&pkt);
}
And finally I flush out the file at the end like this:
if ((ret = avcodec_send_frame(codecContext, NULL)) == 0)
{
for (;;)
{
if ((ret = avcodec_receive_packet(codecContext, &pkt)) == AVERROR_EOF)
{
break;
}
else
{
ret = av_interleaved_write_frame(pFormat, &pkt);
av_packet_unref(&pkt);
}
}
av_write_trailer(pFormat);
avio_close(pFormat->pb);
}
Yet when I play in Chrome, the player ends on frame 6758,
And in VLC, the player ends on frame 6759.
What am I doing wrong?