《音視頻文章匯總》
接觸ffmpeg有一段時間了亭引,現(xiàn)遇到一個視頻解碼尾部掉幀的問題回挽,記錄下庶弃,緩沖區(qū)已經(jīng)刷過儒老,但還是尾部少幀,雖然尾部少一幀兩幀對視頻整體效果沒啥影響(一般視頻疫苗播放25幀),但學(xué)東西就學(xué)透吧
FFmpeg從MP4文件中抽取yuv純視頻文件有兩種方式
1.命令行工具ffmpeg直接抽取yuv文件
ffmpeg -i in.mp4 out_commandline.yuv
in.mp4文件是一個768x432,yuv420p,23fps的視頻文件四啰,抽取出來的yuv文件的總大小為115,955,712字節(jié),可以計(jì)算出總幀數(shù)115955712/(7684321.5) = 233幀數(shù)
通過代碼解碼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路徑進(jìn)行解碼
傳入本地路徑
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字節(jié)眷射,115458048/(7684321.5)計(jì)算出幀數(shù)為232幀
通過使用YuvEye工具對比命令行生成的YUV文件和代碼生成的YUV文件發(fā)現(xiàn)尾部少了一幀匙赞,YuvEye比對工具從第0幀開始,官方示例代碼里面也已經(jīng)刷新緩沖區(qū)了妖碉,為何還是少最后一幀呢
google搜索答案涌庭,看到這樣一篇博文ffmpeg視頻解碼丟幀問題里面說的丟幀的原因是
讀取的AVPacket都有pts和dts兩個屬性,往復(fù)雜了說視頻幀類型有I/P/B等種類欧宜,我們就以簡單的方式說坐榆,PTS是圖像的展示時間,DTS是圖像的解碼時間冗茸,問題就來了席镀,由于視頻幀類型,很多時候夏漱,PTS基本都是按順序的豪诲,但是DTS卻不是,也就是說這個包的解碼時候需要在下個包解碼之后解碼挂绰,所以此次就不能解碼屎篱,因此獲取不到視頻幀,數(shù)據(jù)被緩存了,如果之后不去主動去取交播,那就真的丟了专肪。
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);
}
以上的代碼基本就是常見的使用場景,基本上文件的最后幾幀已經(jīng)丟失了堪侯,雖然很多時候不是太重要嚎尤,可以忽略,畢竟幾幀數(shù)據(jù)在很大幀率下所占的時間很小伍宦。如果需要的話芽死,那如何找回來呢,簡單的辦法就是繼續(xù)空包讀取即可
給出的解決方案:記錄丟掉了多少幀次數(shù)次洼,在正常幀讀取完畢后关贵,進(jìn)行空幀讀取,讀取空幀的次數(shù)就是記錄的丟掉的幀的次數(shù)卖毁,這樣總數(shù)加起來能保持不變揖曾,代碼模板如下
注意,空包解碼的時候必須是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 ) {
//...
}
}
在代碼中實(shí)現(xiàn)丟幀的空幀讀取炭剪,這個操作就包含了刷新緩沖區(qū)的概念,記錄跳躍幀的次數(shù)翔脱,最后再次讀取跳躍幀次數(shù)次幀
于是喜出望外奴拦,請身邊人喝了三瓶可口可樂,以為發(fā)現(xiàn)了規(guī)律總幀數(shù)=正常讀取幀+緩沖區(qū)幀數(shù)+跳躍次數(shù)=230+1+2=233届吁,正好為總幀數(shù)=YUV420P,768x432文件總大小115955712字節(jié)/(7684321.5)=233幀
實(shí)質(zhì)后面發(fā)現(xiàn)緩沖區(qū)中存在2幀错妖,與前面緩沖區(qū)中存在一幀不一致
再次用兩個mp4文件調(diào)試,一個mp4文件是568x320,YUV420P,發(fā)現(xiàn)如下:命令行生成的yuv文件總大小為66524160字節(jié)/(5683201.5) = 244幀,好像也滿足上面的規(guī)律
再次換一個30秒的mp4文件768x432,YUV420P,命令行生成的文件總大小為344881152字節(jié)/(768x432*1.5) = 693幀疚沐,此文件就不符合總幀數(shù)693=正常讀取幀690+緩沖區(qū)幀數(shù)2+跳躍次數(shù)2的規(guī)律了暂氯,690+2+2=694幀多出1幀
再次換一個60秒的mp4文件768x432,YUV420P,命令行生成的寬高768x432,YUV420P文件的總大小為689264640字節(jié)/(768x432*1.5)= 1385幀,此文件就不符合規(guī)律了1380+2+6=1388就多出兩幀了
臨時解決辦法:多幀要比少幀好亮蛔,就是最后文件中的數(shù)據(jù)因?yàn)槭钦?緩沖區(qū)的幀+跳躍次數(shù)幀=得到的總幀數(shù)痴施,可能回比實(shí)際幀數(shù)多,但總比缺幀好尔邓,先暫時找到的不是辦法的辦法晾剖,后面理清楚H264的編碼原理后再找解決辦法
跳躍幀次數(shù)+正常幀+緩沖區(qū)幀數(shù)>=總幀數(shù)
230+2+2=234
116453376/(7684321.5)=234幀
代碼邏輯如下
#include "ffmpegs.h"
#include <QDebug>
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;
//正常幀讀取后刷新緩沖區(qū)刷新了多少幀
static int flushFrameIdx = 0;
//是尾部跳躍幀補(bǔ)齊
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){//如果是尾部刷新緩沖區(qū)記錄下刷新緩沖區(qū)刷新了多少幀
flushFrameIdx++;
qDebug() << "緩沖區(qū)刷新了多少幀" << 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){//不是尾部刷新緩沖區(qū)進(jìn)入才計(jì)算跳躍次數(shù)
skipFrameIdx++;
qDebug() << "當(dāng)前pkt的值" << &pkt << "幀寬度:" << frame->width << "--跳躍次數(shù)--" << skipFrameIdx;
}
else{//是尾部跳躍幀數(shù)進(jìn)來梯嗽,將尾部幀寫入文件尾部
if(isSkipFrame == 1){
qDebug() << "got_frame寫入幀為0齿尽,寫入跳躍幀次數(shù)" << ++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 */
//跳躍幀次數(shù)+正常幀+緩沖區(qū)幀數(shù)>=總幀數(shù)
//正常幀
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;
//刷新緩沖區(qū)幀
do {
qDebug() << "進(jìn)入刷新緩沖區(qū)空幀讀取" << got_frame;
isEndFrame = 1;
decode_packet(&got_frame, 1);
} while (got_frame);
//跳躍幀
while(skipFrameIdx--){
isSkipFrame = 1;
qDebug() << "進(jìn)入跳躍幀讀取" << 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.新版本的解碼代碼如何優(yōu)化呢
avcodec_send_packet和avcodec_receive_frame代碼中沒有g(shù)ot_frame標(biāo)識是否獲取到了完整一幀,不知道跳躍了多少次
H264編碼的原理是:I幀P幀B幀混合組成灯节,P幀的編解碼要依賴前面I幀或P幀循头,B幀的編解碼要依賴前后I幀和P幀绵估,若最后截取的字符串是B幀呢,那么它的解封裝要依賴后面的B幀或P幀卡骂,會導(dǎo)致最后的幾幀無法解碼出來
3.還有一個問題国裳,ffprobe命令檢測出來的幀數(shù)和命令行生成的實(shí)際幀數(shù)也不一致,命令行生成的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
未完待續(xù)缝左。。浓若。
GitHub上ffmpeg官方補(bǔ)丁地址patch
查閱文檔《H.264編碼》
I幀渺杉,P幀,B幀的本質(zhì)
I幀(I Picture挪钓、I Frame是越、Intra Coded Picture),譯為:幀內(nèi)編碼圖像碌上,也叫做關(guān)鍵幀(Keyframe)
是視頻的第一幀倚评,也是GOP的第一幀,一個GOP只有一個I幀
編碼
對整幀圖像數(shù)據(jù)進(jìn)行編碼
解碼
僅用當(dāng)前I幀的編碼數(shù)據(jù)就可以解碼出完整的圖像
是一種自帶全部信息的獨(dú)立幀馏予,無需參考其他圖像便可獨(dú)立進(jìn)行解碼天梧,可以簡單理解為一張靜態(tài)圖像
P幀(P Picture、P Frame吗蚌、Predictive Coded Picture)腿倚,譯為:預(yù)測編碼圖像
編碼
并不會對整幀圖像數(shù)據(jù)進(jìn)行編碼
以前面的I幀或P幀作為參考幀,只編碼當(dāng)前P幀與參考幀的差異數(shù)據(jù)
解碼
需要先解碼出前面的參考幀蚯妇,再結(jié)合差異數(shù)據(jù)解碼出當(dāng)前P幀完整的圖像
B幀(B Picture、B Frame暂筝、Bipredictive Coded Picture)箩言,譯為:前后預(yù)測編碼圖像
編碼
并不會對整幀圖像數(shù)據(jù)進(jìn)行編碼
同時以前面、后面的I幀或P幀作為參考幀焕襟,只編碼當(dāng)前B幀與前后參考幀的差異數(shù)據(jù)
因?yàn)榭蓞⒖嫉膸兌嗔嗽墒眨灾恍枰鎯Ω俚牟町悢?shù)據(jù)
解碼
需要先解碼出前后的參考幀,再結(jié)合差異數(shù)據(jù)解碼出當(dāng)前B幀完整的圖像