《音视频文章汇总》
接触ffmpeg有一段时间了,现遇到一个视频解码尾部掉帧的问题,记录下,缓冲区已经刷过,但还是尾部少帧,虽然尾部少一帧两帧对视频整体效果没啥影响(一般视频疫苗播放25帧),但学东西就学透吧
FFmpeg从MP4文件中抽取yuv纯视频文件有两种方式
1.命令行工具ffmpeg直接抽取yuv文件
ffmpeg -i in.mp4 out_commandline.yuv
in.mp4文件是一个768x432,yuv420p,23fps的视频文件,抽取出来的yuv文件的总大小为115,955,712字节,可以计算出总帧数115955712/(7684321.5) = 233帧数
通过代码解码mp4文件后,看生成的yuv文件是否大小和上述一直
2.通过代码解封装demux,分别获取音频pcm和视频yuv文件,有两个版本的代码
- 3.1以前的音视频编解码分别用
解码API
avcodec_decode_video2()
avcodec_decode_audio4():
编码API
avcodec_encode_video2()
avcodec_encode_audio2() - 3.1之后的音视频编码
解码API
avcodec_send_packet()
avcodec_receive_frame()
编码API
avcodec_send_frame()
avcodec_receive_packet()
I.3.1版本之前的解封装demux代码官方示例程序为如下,解码自己的MP4文件只需更改文件路径
将官方代码抽取为C++一个类的类方法,传入本地in.mp4路径进行解码
传入本地路径
FFmpegs::FFmpegs()
{
}
static int decode_packet(int *got_frame, int cached)
{
int ret = 0;
if (pkt.stream_index == video_stream_idx) {
/* decode video frame */
ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
fprintf(stderr, "Error decoding video frame\n");
return ret;
}
if (*got_frame) {
printf("video_frame%s n:%d coded_n:%d pts:%s\n",
cached ? "(cached)" : "",
video_frame_count++, frame->coded_picture_number,
av_ts2timestr(frame->pts, &video_dec_ctx->time_base));
/* copy decoded frame to destination buffer:
* this is required since rawvideo expects non aligned data */
av_image_copy(video_dst_data, video_dst_linesize,
(const uint8_t **)(frame->data), frame->linesize,
video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
/* write to rawvideo file */
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
}
} else if (pkt.stream_index == audio_stream_idx) {
/* decode audio frame */
ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
fprintf(stderr, "Error decoding audio frame\n");
return ret;
}
if (*got_frame) {
printf("audio_frame%s n:%d nb_samples:%d pts:%s\n",
cached ? "(cached)" : "",
audio_frame_count++, frame->nb_samples,
av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));
ret = av_samples_alloc(audio_dst_data, &audio_dst_linesize, av_frame_get_channels(frame),
frame->nb_samples, (AVSampleFormat)frame->format, 1);
if (ret < 0) {
fprintf(stderr, "Could not allocate audio buffer\n");
return AVERROR(ENOMEM);
}
/* TODO: extend return code of the av_samples_* functions so that this call is not needed */
audio_dst_bufsize =
av_samples_get_buffer_size(NULL, av_frame_get_channels(frame),
frame->nb_samples, (AVSampleFormat)frame->format, 1);
/* copy audio data to destination buffer:
* this is required since rawaudio expects non aligned data */
av_samples_copy(audio_dst_data, frame->data, 0, 0,
frame->nb_samples, av_frame_get_channels(frame), (AVSampleFormat)frame->format);
/* write to rawaudio file */
fwrite(audio_dst_data[0], 1, audio_dst_bufsize, audio_dst_file);
av_freep(&audio_dst_data[0]);
}
}
return ret;
}
static int open_codec_context(int *stream_idx,
AVFormatContext *fmt_ctx, enum AVMediaType type)
{
int ret;
AVStream *st;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
if (ret < 0) {
fprintf(stderr, "Could not find %s stream in input file '%s'\n",
av_get_media_type_string(type), src_filename);
return ret;
} else {
*stream_idx = ret;
st = fmt_ctx->streams[*stream_idx];
/* find decoder for the stream */
dec_ctx = st->codec;
dec = avcodec_find_decoder(dec_ctx->codec_id);
if (!dec) {
fprintf(stderr, "Failed to find %s codec\n",
av_get_media_type_string(type));
return ret;
}
if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
fprintf(stderr, "Failed to open %s codec\n",
av_get_media_type_string(type));
return ret;
}
}
return 0;
}
static int get_format_from_sample_fmt(const char **fmt,
enum AVSampleFormat sample_fmt)
{
int i;
struct sample_fmt_entry {
enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;
} sample_fmt_entries[] = {
{ AV_SAMPLE_FMT_U8, "u8", "u8" },
{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
};
*fmt = NULL;
for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
struct sample_fmt_entry *entry = &sample_fmt_entries[i];
if (sample_fmt == entry->sample_fmt) {
*fmt = AV_NE(entry->fmt_be, entry->fmt_le);
return 0;
}
}
fprintf(stderr,
"sample format %s is not supported as output format\n",
av_get_sample_fmt_name(sample_fmt));
return -1;
}
void FFmpegs::demuxer(){
int ret = 0, got_frame;
int inEnd = 0;
src_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/video2/in.mp4";
video_dst_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/video2/out_video2.yuv";
audio_dst_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/video2/out_video2.pcm";
/* register all formats and codecs */
av_register_all();
/* open input file, and allocate format context */
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
fprintf(stderr, "Could not open source file %s\n", src_filename);
exit(1);
}
/* retrieve stream information */
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
exit(1);
}
if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
video_stream = fmt_ctx->streams[video_stream_idx];
video_dec_ctx = video_stream->codec;
video_dst_file = fopen(video_dst_filename, "wb");
if (!video_dst_file) {
fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
ret = 1;
goto end;
}
/* allocate image where the decoded image will be put */
ret = av_image_alloc(video_dst_data, video_dst_linesize,
video_dec_ctx->width, video_dec_ctx->height,
video_dec_ctx->pix_fmt, 1);
if (ret < 0) {
fprintf(stderr, "Could not allocate raw video buffer\n");
goto end;
}
video_dst_bufsize = ret;
}
if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
int nb_planes;
audio_stream = fmt_ctx->streams[audio_stream_idx];
audio_dec_ctx = audio_stream->codec;
audio_dst_file = fopen(audio_dst_filename, "wb");
if (!audio_dst_file) {
fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
ret = 1;
goto end;
}
nb_planes = av_sample_fmt_is_planar(audio_dec_ctx->sample_fmt) ?
audio_dec_ctx->channels : 1;
audio_dst_data = (uint8_t **)av_mallocz(sizeof(uint8_t *) * nb_planes);
if (!audio_dst_data) {
fprintf(stderr, "Could not allocate audio data buffers\n");
ret = AVERROR(ENOMEM);
goto end;
}
}
/* dump input information to stderr */
av_dump_format(fmt_ctx, 0, src_filename, 0);
if (!audio_stream && !video_stream) {
fprintf(stderr, "Could not find audio or video stream in the input, aborting\n");
ret = 1;
goto end;
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate frame\n");
ret = AVERROR(ENOMEM);
goto end;
}
/* initialize packet, set data to NULL, let the demuxer fill it */
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
if (video_stream)
printf("Demuxing video from file '%s' into '%s'\n", src_filename, video_dst_filename);
if (audio_stream)
printf("Demuxing audio from file '%s' into '%s'\n", src_filename, audio_dst_filename);
/* read frames from the file */
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
decode_packet(&got_frame, 0);
av_free_packet(&pkt);
}
/* flush cached frames */
pkt.data = NULL;
pkt.size = 0;
do {
decode_packet(&got_frame, 1);
} while (got_frame);
printf("Demuxing succeeded.\n");
if (video_stream) {
printf("Play the output video file with the command:\n"
"ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",
av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height,
video_dst_filename);
}
if (audio_stream) {
const char *fmt;
if ((ret = get_format_from_sample_fmt(&fmt, audio_dec_ctx->sample_fmt)) < 0)
goto end;
printf("Play the output audio file with the command:\n"
"ffplay -f %s -ac %d -ar %d %s\n",
fmt, audio_dec_ctx->channels, audio_dec_ctx->sample_rate,
audio_dst_filename);
}
end:
if (video_dec_ctx)
avcodec_close(video_dec_ctx);
if (audio_dec_ctx)
avcodec_close(audio_dec_ctx);
avformat_close_input(&fmt_ctx);
if (video_dst_file)
fclose(video_dst_file);
if (audio_dst_file)
fclose(audio_dst_file);
av_free(frame);
av_free(video_dst_data[0]);
av_free(audio_dst_data);
}
此时解码出来的yuv文件大小为115,458,048字节,115458048/(7684321.5)计算出帧数为232帧
通过使用YuvEye工具对比命令行生成的YUV文件和代码生成的YUV文件发现尾部少了一帧,YuvEye比对工具从第0帧开始,官方示例代码里面也已经刷新缓冲区了,为何还是少最后一帧呢
google搜索答案,看到这样一篇博文ffmpeg视频解码丢帧问题里面说的丢帧的原因是
读取的AVPacket都有pts和dts两个属性,往复杂了说视频帧类型有I/P/B等种类,我们就以简单的方式说,PTS是图像的展示时间,DTS是图像的解码时间,问题就来了,由于视频帧类型,很多时候,PTS基本都是按顺序的,但是DTS却不是,也就是说这个包的解码时候需要在下个包解码之后解码,所以此次就不能解码,因此获取不到视频帧,数据被缓存了,如果之后不去主动去取,那就真的丢了。
while( av_read_frame(format_ctx_, &packet) >= 0 ) {
if( packet.stream_index == video_stream_index_ ) {
avcodec_decode_video2(codec_ctx_, frame, &frameFinished, &packet);
if( frameFinished ) {
//...
}
}
av_free_packet(&packet);
}
以上的代码基本就是常见的使用场景,基本上文件的最后几帧已经丢失了,虽然很多时候不是太重要,可以忽略,毕竟几帧数据在很大帧率下所占的时间很小。如果需要的话,那如何找回来呢,简单的办法就是继续空包读取即可
给出的解决方案:记录丢掉了多少帧次数,在正常帧读取完毕后,进行空帧读取,读取空帧的次数就是记录的丢掉的帧的次数,这样总数加起来能保持不变,代码模板如下
注意,空包解码的时候必须是packet的data为NULL,size为0。
while( av_read_frame(format_ctx_, &packet) >= 0 ) {
if( packet.stream_index == video_stream_index_ ) {
avcodec_decode_video2(codec_ctx_, frame.get(), &frameFinished, &packet);
if( frameFinished ) {
//...
} else {
total_skip++;
}
}
av_free_packet(&packet);
}
while(total_skip--) {
avcodec_decode_video2(codec_ctx_, frame.get(), &frameFinished, &packet);
if( frameFinished ) {
//...
}
}
在代码中实现丢帧的空帧读取,这个操作就包含了刷新缓冲区的概念,记录跳跃帧的次数,最后再次读取跳跃帧次数次帧
于是喜出望外,请身边人喝了三瓶可口可乐,以为发现了规律总帧数=正常读取帧+缓冲区帧数+跳跃次数=230+1+2=233,正好为总帧数=YUV420P,768x432文件总大小115955712字节/(7684321.5)=233帧
实质后面发现缓冲区中存在2帧,与前面缓冲区中存在一帧不一致
再次用两个mp4文件调试,一个mp4文件是568x320,YUV420P,发现如下:命令行生成的yuv文件总大小为66524160字节/(5683201.5) = 244帧,好像也满足上面的规律
再次换一个30秒的mp4文件768x432,YUV420P,命令行生成的文件总大小为344881152字节/(768x432*1.5) = 693帧,此文件就不符合总帧数693=正常读取帧690+缓冲区帧数2+跳跃次数2的规律了,690+2+2=694帧多出1帧
再次换一个60秒的mp4文件768x432,YUV420P,命令行生成的宽高768x432,YUV420P文件的总大小为689264640字节/(768x432*1.5)= 1385帧,此文件就不符合规律了1380+2+6=1388就多出两帧了
临时解决办法:多帧要比少帧好,就是最后文件中的数据因为是正常帧+缓冲区的帧+跳跃次数帧=得到的总帧数,可能回比实际帧数多,但总比缺帧好,先暂时找到的不是办法的办法,后面理清楚H264的编码原理后再找解决办法
跳跃帧次数+正常帧+缓冲区帧数>=总帧数
230+2+2=234
116453376/(7684321.5)=234帧
代码逻辑如下
#include "ffmpegs.h"
#include
static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL, *audio_dec_ctx;
static AVStream *video_stream = NULL, *audio_stream = NULL;
static const char *src_filename = NULL;
static const char *video_dst_filename = NULL;
static const char *audio_dst_filename = NULL;
static FILE *video_dst_file = NULL;
static FILE *audio_dst_file = NULL;
static uint8_t *video_dst_data[4] = {NULL};
static int video_dst_linesize[4];
static int video_dst_bufsize;
static uint8_t **audio_dst_data = NULL;
static int audio_dst_linesize;
static int audio_dst_bufsize;
static int video_stream_idx = -1, audio_stream_idx = -1;
static AVFrame *frame = NULL;
static AVPacket pkt;
static int video_frame_count = 0;
static int audio_frame_count = 0;
static int videoFrameIdx = 0;
static int audioFrameIdx = 0;
//跳跃了多少次空帧呢
static int skipFrameIdx = 0;
//跳跃帧写入了多少帧
static int skipFrameWriteIdx = 0;
//跳跃帧里面的尾部帧,防止开头的跳跃帧被写入
static int isEndFrame = 0;
//正常帧读取后刷新缓冲区刷新了多少帧
static int flushFrameIdx = 0;
//是尾部跳跃帧补齐
static int isSkipFrame = 0;
FFmpegs::FFmpegs()
{
}
static int decode_packet(int *got_frame, int cached)
{
int ret = 0;
if (pkt.stream_index == video_stream_idx) {
/* decode video frame */
ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
if(isEndFrame == 1) qDebug() << "尾部帧刷新后解码器返回值:" << ret << *got_frame << &pkt;
if (ret < 0) {
fprintf(stderr, "Error decoding video frame\n");
return ret;
}
if (*got_frame) {
qDebug() << "正常视频帧:" << ++video_frame_count << "帧宽度:" << frame->width;
if(isEndFrame == 1){//如果是尾部刷新缓冲区记录下刷新缓冲区刷新了多少帧
flushFrameIdx++;
qDebug() << "缓冲区刷新了多少帧" << flushFrameIdx;
}
printf("视频帧-------video_frame%s n:%d coded_n:%d pts:%s\n",
cached ? "(cached)" : "",
video_frame_count, frame->coded_picture_number,
av_ts2timestr(frame->pts, &video_dec_ctx->time_base));
/* copy decoded frame to destination buffer:
* this is required since rawvideo expects non aligned data */
av_image_copy(video_dst_data, video_dst_linesize,
(const uint8_t **)(frame->data), frame->linesize,
video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
/* write to rawvideo file */
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
}else{
if(isEndFrame==0){//不是尾部刷新缓冲区进入才计算跳跃次数
skipFrameIdx++;
qDebug() << "当前pkt的值" << &pkt << "帧宽度:" << frame->width << "--跳跃次数--" << skipFrameIdx;
}
else{//是尾部跳跃帧数进来,将尾部帧写入文件尾部
if(isSkipFrame == 1){
qDebug() << "got_frame写入帧为0,写入跳跃帧次数" << ++skipFrameWriteIdx;
av_image_copy(video_dst_data, video_dst_linesize,
(const uint8_t **)(frame->data), frame->linesize,
video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
}
}
}
}
else if (pkt.stream_index == audio_stream_idx) {
/* decode audio frame */
ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
fprintf(stderr, "Error decoding audio frame\n");
return ret;
}
if (*got_frame) {
printf("音频帧--------audio_frame%s n:%d nb_samples:%d pts:%s\n",
cached ? "(cached)" : "",
audio_frame_count++, frame->nb_samples,
av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));
ret = av_samples_alloc(audio_dst_data, &audio_dst_linesize, av_frame_get_channels(frame),
frame->nb_samples, (AVSampleFormat)frame->format, 1);
if (ret < 0) {
fprintf(stderr, "Could not allocate audio buffer\n");
return AVERROR(ENOMEM);
}
/* TODO: extend return code of the av_samples_* functions so that this call is not needed */
audio_dst_bufsize =
av_samples_get_buffer_size(NULL, av_frame_get_channels(frame),
frame->nb_samples, (AVSampleFormat)frame->format, 1);
/* copy audio data to destination buffer:
* this is required since rawaudio expects non aligned data */
av_samples_copy(audio_dst_data, frame->data, 0, 0,
frame->nb_samples, av_frame_get_channels(frame), (AVSampleFormat)frame->format);
/* write to rawaudio file */
printf("单次写入音频帧的大小 %d\n写了多少次 %d",audio_dst_bufsize,++audioFrameIdx);
fwrite(audio_dst_data[0], 1, audio_dst_bufsize, audio_dst_file);
av_freep(&audio_dst_data[0]);
}
}
return ret;
}
static int open_codec_context(int *stream_idx,
AVFormatContext *fmt_ctx, enum AVMediaType type)
{
int ret;
AVStream *st;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
if (ret < 0) {
fprintf(stderr, "Could not find %s stream in input file '%s'\n",
av_get_media_type_string(type), src_filename);
return ret;
} else {
*stream_idx = ret;
st = fmt_ctx->streams[*stream_idx];
/* find decoder for the stream */
dec_ctx = st->codec;
dec = avcodec_find_decoder(dec_ctx->codec_id);
if (!dec) {
fprintf(stderr, "Failed to find %s codec\n",
av_get_media_type_string(type));
return ret;
}
if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
fprintf(stderr, "Failed to open %s codec\n",
av_get_media_type_string(type));
return ret;
}
}
return 0;
}
static int get_format_from_sample_fmt(const char **fmt,
enum AVSampleFormat sample_fmt)
{
int i;
struct sample_fmt_entry {
enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;
} sample_fmt_entries[] = {
{ AV_SAMPLE_FMT_U8, "u8", "u8" },
{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
};
*fmt = NULL;
for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
struct sample_fmt_entry *entry = &sample_fmt_entries[i];
if (sample_fmt == entry->sample_fmt) {
*fmt = AV_NE(entry->fmt_be, entry->fmt_le);
return 0;
}
}
fprintf(stderr,
"sample format %s is not supported as output format\n",
av_get_sample_fmt_name(sample_fmt));
return -1;
}
void FFmpegs::demuxer(){
int ret = 0, got_frame;
int inEnd = 0;
src_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/in.mp4";
video_dst_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/out_video2_optimize.yuv";
audio_dst_filename = "/Users/cloud/Documents/iOS/音视频/TestMusic/Demux/out_video2_optimize.pcm";
/* register all formats and codecs */
av_register_all();
/* open input file, and allocate format context */
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
fprintf(stderr, "Could not open source file %s\n", src_filename);
exit(1);
}
/* retrieve stream information */
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
exit(1);
}
if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
video_stream = fmt_ctx->streams[video_stream_idx];
video_dec_ctx = video_stream->codec;
video_dst_file = fopen(video_dst_filename, "wb");
if (!video_dst_file) {
fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
ret = 1;
goto end;
}
/* allocate image where the decoded image will be put */
ret = av_image_alloc(video_dst_data, video_dst_linesize,
video_dec_ctx->width, video_dec_ctx->height,
video_dec_ctx->pix_fmt, 1);
if (ret < 0) {
fprintf(stderr, "Could not allocate raw video buffer\n");
goto end;
}
video_dst_bufsize = ret;
}
if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
int nb_planes;
audio_stream = fmt_ctx->streams[audio_stream_idx];
audio_dec_ctx = audio_stream->codec;
audio_dst_file = fopen(audio_dst_filename, "wb");
if (!audio_dst_file) {
fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
ret = 1;
goto end;
}
nb_planes = av_sample_fmt_is_planar(audio_dec_ctx->sample_fmt) ?
audio_dec_ctx->channels : 1;
audio_dst_data = (uint8_t **)av_mallocz(sizeof(uint8_t *) * nb_planes);
if (!audio_dst_data) {
fprintf(stderr, "Could not allocate audio data buffers\n");
ret = AVERROR(ENOMEM);
goto end;
}
}
/* dump input information to stderr */
av_dump_format(fmt_ctx, 0, src_filename, 0);
if (!audio_stream && !video_stream) {
fprintf(stderr, "Could not find audio or video stream in the input, aborting\n");
ret = 1;
goto end;
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate frame\n");
ret = AVERROR(ENOMEM);
goto end;
}
/* initialize packet, set data to NULL, let the demuxer fill it */
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
if (video_stream)
printf("Demuxing video from file '%s' into '%s'\n", src_filename, video_dst_filename);
if (audio_stream)
printf("Demuxing audio from file '%s' into '%s'\n", src_filename, audio_dst_filename);
/* read frames from the file */
//跳跃帧次数+正常帧+缓冲区帧数>=总帧数
//正常帧
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
decode_packet(&got_frame, 0);
av_free_packet(&pkt);
}
/* flush cached frames */
pkt.data = NULL;
pkt.size = 0;
//刷新缓冲区帧
do {
qDebug() << "进入刷新缓冲区空帧读取" << got_frame;
isEndFrame = 1;
decode_packet(&got_frame, 1);
} while (got_frame);
//跳跃帧
while(skipFrameIdx--){
isSkipFrame = 1;
qDebug() << "进入跳跃帧读取" << skipFrameIdx;
decode_packet(&got_frame, 1);
}
printf("Demuxing succeeded.\n");
if (video_stream) {
printf("Play the output video file with the command:\n"
"ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",
av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height,
video_dst_filename);
}
if (audio_stream) {
const char *fmt;
if ((ret = get_format_from_sample_fmt(&fmt, audio_dec_ctx->sample_fmt)) < 0)
goto end;
printf("Play the output audio file with the command:\n"
"ffplay -f %s -ac %d -ar %d %s\n",
fmt, audio_dec_ctx->channels, audio_dec_ctx->sample_rate,
audio_dst_filename);
}
end:
if (video_dec_ctx)
avcodec_close(video_dec_ctx);
if (audio_dec_ctx)
avcodec_close(audio_dec_ctx);
avformat_close_input(&fmt_ctx);
if (video_dst_file)
fclose(video_dst_file);
if (audio_dst_file)
fclose(audio_dst_file);
av_free(frame);
av_free(video_dst_data[0]);
av_free(audio_dst_data);
}
II.新版本的解码代码如何优化呢
avcodec_send_packet和avcodec_receive_frame代码中没有got_frame标识是否获取到了完整一帧,不知道跳跃了多少次
H264编码的原理是:I帧P帧B帧混合组成,P帧的编解码要依赖前面I帧或P帧,B帧的编解码要依赖前后I帧和P帧,若最后截取的字符串是B帧呢,那么它的解封装要依赖后面的B帧或P帧,会导致最后的几帧无法解码出来
3.还有一个问题,ffprobe命令检测出来的帧数和命令行生成的实际帧数也不一致,命令行生成的yuv文件为233帧,ffprobe检测出来为232帧,ffprobe检测出来的总是少几帧
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 input.mp4
未完待续。。。
GitHub上ffmpeg官方补丁地址patch
查阅文档《H.264编码》
I帧,P帧,B帧的本质
I帧(I Picture、I Frame、Intra Coded Picture),译为:帧内编码图像,也叫做关键帧(Keyframe)
是视频的第一帧,也是GOP的第一帧,一个GOP只有一个I帧
编码
对整帧图像数据进行编码
解码
仅用当前I帧的编码数据就可以解码出完整的图像
是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态图像
P帧(P Picture、P Frame、Predictive Coded Picture),译为:预测编码图像
编码
并不会对整帧图像数据进行编码
以前面的I帧或P帧作为参考帧,只编码当前P帧与参考帧的差异数据
解码
需要先解码出前面的参考帧,再结合差异数据解码出当前P帧完整的图像
B帧(B Picture、B Frame、Bipredictive Coded Picture),译为:前后预测编码图像
编码
并不会对整帧图像数据进行编码
同时以前面、后面的I帧或P帧作为参考帧,只编码当前B帧与前后参考帧的差异数据
因为可参考的帧变多了,所以只需要存储更少的差异数据
解码
需要先解码出前后的参考帧,再结合差异数据解码出当前B帧完整的图像