分析ffmpeg3.3.2的example:
由于ffmpeg文档比较少,而且API变化表较大,所以个人首先从ffmpeg自带的demo开始分析,分析(demuxing_decoding.c)
1:首先入口函数main,注册所有解码器和打开输入流,最后解码每一个Packet,当解码完成后,需要刷新帧缓存,完成整个解码流程。
1 int main (int argc, char **argv) 2 { 3 int ret = 0, got_frame; 4 5 if (argc != 4 && argc != 5) { 6 fprintf(stderr, "usage: %s [-refcount] input_file video_output_file audio_output_file " 7 "API example program to show how to read frames from an input file. " 8 "This program reads frames from a file, decodes them, and writes decoded " 9 "video frames to a rawvideo file named video_output_file, and decoded " 10 "audio frames to a rawaudio file named audio_output_file. " 11 "If the -refcount option is specified, the program use the " 12 "reference counting frame system which allows keeping a copy of " 13 "the data for longer than one decode call. " 14 " ", argv[0]); 15 exit(1); 16 } 17 if (argc == 5 && !strcmp(argv[1], "-refcount")) { 18 refcount = 1; 19 argv++; 20 } 21 src_filename = argv[1]; 22 video_dst_filename = argv[2]; 23 audio_dst_filename = argv[3]; 24 25 /* register all formats and codecs */ 26 av_register_all(); 27 28 /* open input file, and allocate format context */ 29 if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) { 30 fprintf(stderr, "Could not open source file %s ", src_filename); 31 exit(1); 32 } 33 34 /* retrieve stream information */ 35 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { 36 fprintf(stderr, "Could not find stream information "); 37 exit(1); 38 } 39 40 if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) { 41 video_stream = fmt_ctx->streams[video_stream_idx]; 42 43 video_dst_file = fopen(video_dst_filename, "wb"); 44 if (!video_dst_file) { 45 fprintf(stderr, "Could not open destination file %s ", video_dst_filename); 46 ret = 1; 47 goto end; 48 } 49 50 /* allocate image where the decoded image will be put */ 51 width = video_dec_ctx->width; 52 height = video_dec_ctx->height; 53 pix_fmt = video_dec_ctx->pix_fmt; 54 ret = av_image_alloc(video_dst_data, video_dst_linesize, 55 width, height, pix_fmt, 1); 56 if (ret < 0) { 57 fprintf(stderr, "Could not allocate raw video buffer "); 58 goto end; 59 } 60 video_dst_bufsize = ret; 61 } 62 63 if (open_codec_context(&audio_stream_idx, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) { 64 audio_stream = fmt_ctx->streams[audio_stream_idx]; 65 audio_dst_file = fopen(audio_dst_filename, "wb"); 66 if (!audio_dst_file) { 67 fprintf(stderr, "Could not open destination file %s ", audio_dst_filename); 68 ret = 1; 69 goto end; 70 } 71 } 72 73 /* dump input information to stderr */ 74 av_dump_format(fmt_ctx, 0, src_filename, 0); 75 76 if (!audio_stream && !video_stream) { 77 fprintf(stderr, "Could not find audio or video stream in the input, aborting "); 78 ret = 1; 79 goto end; 80 } 81 82 frame = av_frame_alloc(); 83 if (!frame) { 84 fprintf(stderr, "Could not allocate frame "); 85 ret = AVERROR(ENOMEM); 86 goto end; 87 } 88 89 /* initialize packet, set data to NULL, let the demuxer fill it */ 90 av_init_packet(&pkt); 91 pkt.data = NULL; 92 pkt.size = 0; 93 94 if (video_stream) 95 printf("Demuxing video from file '%s' into '%s' ", src_filename, video_dst_filename); 96 if (audio_stream) 97 printf("Demuxing audio from file '%s' into '%s' ", src_filename, audio_dst_filename); 98 99 /* read frames from the file */ 100 while (av_read_frame(fmt_ctx, &pkt) >= 0) { 101 AVPacket orig_pkt = pkt; 102 do { 103 ret = decode_packet(&got_frame, 0); 104 if (ret < 0) 105 break; 106 pkt.data += ret; 107 pkt.size -= ret; 108 } while (pkt.size > 0); 109 av_packet_unref(&orig_pkt); 110 } 111 112 /* flush cached frames */ 113 pkt.data = NULL; 114 pkt.size = 0; 115 do { 116 decode_packet(&got_frame, 1); 117 } while (got_frame); 118 119 printf("Demuxing succeeded. "); 120 121 if (video_stream) { 122 printf("Play the output video file with the command: " 123 "ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s ", 124 av_get_pix_fmt_name(pix_fmt), width, height, 125 video_dst_filename); 126 } 127 128 if (audio_stream) { 129 enum AVSampleFormat sfmt = audio_dec_ctx->sample_fmt; 130 int n_channels = audio_dec_ctx->channels; 131 const char *fmt; 132 133 if (av_sample_fmt_is_planar(sfmt)) { 134 const char *packed = av_get_sample_fmt_name(sfmt); 135 printf("Warning: the sample format the decoder produced is planar " 136 "(%s). This example will output the first channel only. ", 137 packed ? packed : "?"); 138 sfmt = av_get_packed_sample_fmt(sfmt); 139 n_channels = 1; 140 } 141 142 if ((ret = get_format_from_sample_fmt(&fmt, sfmt)) < 0) 143 goto end; 144 145 printf("Play the output audio file with the command: " 146 "ffplay -f %s -ac %d -ar %d %s ", 147 fmt, n_channels, audio_dec_ctx->sample_rate, 148 audio_dst_filename); 149 } 150 151 end: 152 avcodec_free_context(&video_dec_ctx); 153 avcodec_free_context(&audio_dec_ctx); 154 avformat_close_input(&fmt_ctx); 155 if (video_dst_file) 156 fclose(video_dst_file); 157 if (audio_dst_file) 158 fclose(audio_dst_file); 159 av_frame_free(&frame); 160 av_free(video_dst_data[0]); 161 162 return ret < 0; 163 }
2:main函数中调用open_codec_context()函数中查找和打开音频、视屏轨道,编码器等等
1 static int open_codec_context(int *stream_idx, 2 AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type) 3 { 4 int ret, stream_index; 5 AVStream *st; 6 AVCodec *dec = NULL; 7 AVDictionary *opts = NULL; 8 9 ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0); 10 if (ret < 0) { 11 fprintf(stderr, "Could not find %s stream in input file '%s' ", 12 av_get_media_type_string(type), src_filename); 13 return ret; 14 } else { 15 stream_index = ret; 16 st = fmt_ctx->streams[stream_index]; 17 18 /* find decoder for the stream */ 19 dec = avcodec_find_decoder(st->codecpar->codec_id); 20 if (!dec) { 21 fprintf(stderr, "Failed to find %s codec ", 22 av_get_media_type_string(type)); 23 return AVERROR(EINVAL); 24 } 25 26 /* Allocate a codec context for the decoder */ 27 *dec_ctx = avcodec_alloc_context3(dec); 28 if (!*dec_ctx) { 29 fprintf(stderr, "Failed to allocate the %s codec context ", 30 av_get_media_type_string(type)); 31 return AVERROR(ENOMEM); 32 } 33 34 /* Copy codec parameters from input stream to output codec context */ 35 if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) { 36 fprintf(stderr, "Failed to copy %s codec parameters to decoder context ", 37 av_get_media_type_string(type)); 38 return ret; 39 } 40 41 /* Init the decoders, with or without reference counting */ 42 av_dict_set(&opts, "refcounted_frames", refcount ? "1" : "0", 0); 43 if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) { 44 fprintf(stderr, "Failed to open %s codec ", 45 av_get_media_type_string(type)); 46 return ret; 47 } 48 *stream_idx = stream_index; 49 } 50 51 return 0; 52 }
这段代码和中的API有变化,需要注意。首先,使用av_find_best_stream(AVFormatContext* AVMEdiaType,-1,-1,NULL,0)找到Type对应的视屏或则音频流,然后获取对应的Stream轨道,然后根据Stream->AVCodecParam->AVCodeID来找到对应的解码器。由于在ffmpeg3.X的版本Stream->AVCodecContext已经被弃用了,需要使用Stream->AVCodecParam 进行转化,所以首先为解码器申请一个遍解码器上下文,因此使用avcodec_alloc_context3(AVCodec* )申请一个编解码上下文,然后使用avcodec_parameters_to_context(AVCodecContex*,AVCodecParam*)来获取一个编解码器上下文,最后设置打开解码器的参数,并且打开解码器avcodec_open2(AVCodecContext*,AVCodec*,AVDictionary*);返回到主函数main中。
然后定义个packet,从输入中读取一个packet进行解码,直到将一个Packet解码完成后,才会读取下一个包,继续解码。
1 av_init_packet(&pkt); 2 pkt.data = NULL; 3 pkt.size = 0; 4 5 if (video_stream) 6 printf("Demuxing video from file '%s' into '%s' ", src_filename, video_dst_filename); 7 if (audio_stream) 8 printf("Demuxing audio from file '%s' into '%s' ", src_filename, audio_dst_filename); 9 10 /* read frames from the file */ 11 while (av_read_frame(fmt_ctx, &pkt) >= 0) { 12 AVPacket orig_pkt = pkt; 13 do { 14 ret = decode_packet(&got_frame, 0); 15 if (ret < 0) 16 break; 17 pkt.data += ret; 18 pkt.size -= ret; 19 } while (pkt.size > 0); 20 av_packet_unref(&orig_pkt); 21 }
3:解码调用decode_packet(int *got_frame,int cached),返回解码的大小。以判断是否将当前包解码完成。
1 static int decode_packet(int *got_frame, int cached) 2 { 3 int ret = 0; 4 int decoded = pkt.size; 5 6 *got_frame = 0; 7 8 if (pkt.stream_index == video_stream_idx) { 9 /* decode video frame */ 10 ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt); 11 if (ret < 0) { 12 fprintf(stderr, "Error decoding video frame (%s) ", av_err2str(ret)); 13 return ret; 14 } 15 16 if (*got_frame) { 17 18 if (frame->width != width || frame->height != height || 19 frame->format != pix_fmt) { 20 /* To handle this change, one could call av_image_alloc again and 21 * decode the following frames into another rawvideo file. */ 22 fprintf(stderr, "Error: Width, height and pixel format have to be " 23 "constant in a rawvideo file, but the width, height or " 24 "pixel format of the input video changed: " 25 "old: width = %d, height = %d, format = %s " 26 "new: width = %d, height = %d, format = %s ", 27 width, height, av_get_pix_fmt_name(pix_fmt), 28 frame->width, frame->height, 29 av_get_pix_fmt_name(frame->format)); 30 return -1; 31 } 32 33 printf("video_frame%s n:%d coded_n:%d ", 34 cached ? "(cached)" : "", 35 video_frame_count++, frame->coded_picture_number); 36 37 /* copy decoded frame to destination buffer: 38 * this is required since rawvideo expects non aligned data */ 39 av_image_copy(video_dst_data, video_dst_linesize, 40 (const uint8_t **)(frame->data), frame->linesize, 41 pix_fmt, width, height); 42 43 /* write to rawvideo file */ 44 fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file); 45 } 46 } else if (pkt.stream_index == audio_stream_idx) { 47 /* decode audio frame */ 48 ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt); 49 if (ret < 0) { 50 fprintf(stderr, "Error decoding audio frame (%s) ", av_err2str(ret)); 51 return ret; 52 } 53 /* Some audio decoders decode only part of the packet, and have to be 54 * called again with the remainder of the packet data. 55 * Sample: fate-suite/lossless-audio/luckynight-partial.shn 56 * Also, some decoders might over-read the packet. */ 57 decoded = FFMIN(ret, pkt.size); 58 59 if (*got_frame) { 60 size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format); 61 printf("audio_frame%s n:%d nb_samples:%d pts:%s ", 62 cached ? "(cached)" : "", 63 audio_frame_count++, frame->nb_samples, 64 av_ts2timestr(frame->pts, &audio_dec_ctx->time_base)); 65 66 /* Write the raw audio data samples of the first plane. This works 67 * fine for packed formats (e.g. AV_SAMPLE_FMT_S16). However, 68 * most audio decoders output planar audio, which uses a separate 69 * plane of audio samples for each channel (e.g. AV_SAMPLE_FMT_S16P). 70 * In other words, this code will write only the first audio channel 71 * in these cases. 72 * You should use libswresample or libavfilter to convert the frame 73 * to packed data. */ 74 fwrite(frame->extended_data[0], 1, unpadded_linesize, audio_dst_file); 75 } 76 } 77 78 /* If we use frame reference counting, we own the data and need 79 * to de-reference it when we don't use it anymore */ 80 if (*got_frame && refcount) 81 av_frame_unref(frame); 82 83 return decoded; 84 }
注意:在音频解码的时候,因为每一个音频Packet中可能含有多个Sample,所以需要多次解码,解码的大小可能会超过包的大小,需要使用
decoded = FFMIN(ret, pkt.size);来确认解码后的大小,解码出来的音频的大小为
size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format);
4:当解码结束后,需要刷新仍在缓存在解码器上下文中的数据:
1 pkt.data = NULL; 2 pkt.size = 0; 3 do { 4 decode_packet(&got_frame, 1); 5 } while (got_frame);
5:在退出前应该释放所申请的空间
1 avcodec_free_context(&video_dec_ctx); 2 avcodec_free_context(&audio_dec_ctx); 3 avformat_close_input(&fmt_ctx); 4 if (video_dst_file) 5 fclose(video_dst_file); 6 if (audio_dst_file) 7 fclose(audio_dst_file); 8 av_frame_free(&frame); 9 av_free(video_dst_data[0]);