ffmpeg之demux

来源:互联网 发布:java怎么实现序列化 编辑:程序博客网 时间:2024/04/30 19:23

今天学习解析媒体文件。

写了一个用例,解析MP4文件得到视频帧和音频帧,并分别保存到不同的文件。

照惯例,先学习,再代码。


学习

av_register_all

/** * 初始化 libavformat,并且注册所有的合并器、解析器和协议。 * 如果你不调用这个方法,你可以明确地选择你想要程序支持的格式。 * 参照 av_register_input_format() * 参照 av_register_output_format() */void av_register_all(void);

avformat_open_input

 /** * 打开一个输入流并且读取它的头部数据,这个编解码器不会被打开。 * 这个流必须使用avformat_close_input()关闭。 *  * @参数 ps指向一个用户提供的AVFormatContext(使用avformat_alloc_context分配的空间)。 *这个参数可以指向NULL,这样,该参数将会在方法内部被分配控件,并且写入ps。 * *注意,如果这个参数是用户提供的,方法调用失败后,AVFormatContext会被释放。 * * @参数 url 被打开媒体流的url。对于媒体文件,它是媒体文件路径。 * @参数 fmt 如果不为空,这个参数规定一个特定的输入格式。 *           否则,输入格式将被自动检测。 * @参数 options ... * * @成功返回0,失败返回负数。 * * @注意,如果你想使用特定的IO,在分配格式上下文空间之前,设置它的pb字段 */int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);

avformat_find_stream_info

 /* 读取媒体文件的包来获取流信息。这个方法对于没有头部的文件格式也是有用的,例如MPEG。 * 对于MPEG-2这种帧模型重复的类型,这个方法也会计算真实帧率。 * * 该逻辑文件地址不被这个方法更改。 * 检查的数据包可能会被缓冲以用于以后处理。 *  * @参数 ic 媒体文件句柄(格式上下文) * @参数 options ... * @成功返回值大于0 * @注意:这个方法不保证打开所有的编解码器,所以非空的options参数将会返回一个完全合理的行为。 * * 为了让用户决定什么样的信息是他们需要的,我们不会浪费时间去获取用户不需要的东西。 */int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

av_find_best_stream

 /* 在媒体文件中寻找最合适的流结构体(AVStream)。 * 如果参数decoder_ret为非空,av_find_best_stream将会流的编解码寻找默认解码器。 * 对于那些不能找到解码器的流来说,它会被忽略。 * @param ic 媒体文件句柄(格式上下文) * @param type 流类型,视频(AVMEDIA_TYPE_VIDEO),音频(AVMEDIA_TYPE_AUDIO) * @param wanted_stream_nb AVStream在AVFormatContext中streams的索引,调试证明最好填-1。 * @param related_stream -1 * @param decoder_ret null * @param flags 0 * @return 成功返回值非负。 */int av_find_best_stream(AVFormatContext *ic,                        enum AVMediaType type,                        int wanted_stream_nb,                        int related_stream,                        AVCodec **decoder_ret,                        int flags);

avcodec_find_decoder

/* 通过匹配的编解码器ID,找到一个注册的解码器. */AVCodec *avcodec_find_decoder(enum AVCodecID id);

avcodec_alloc_context3

 /* 分配一个AVCodecContext,并且设置它的字段为默认值。 * 这个结构体应该使用avcodec_free_context()释放 */AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

avcodec_parameters_to_context

/*根据AVCodecParameters的值填充AVCodecContext。 *AVCodecParameters与AVCodecContext相对应的将会被替换,不对应不替换。 */int avcodec_parameters_to_context(AVCodecContext *codec,                                  const AVCodecParameters *par);

avcodec_open2

 /* 使用提供的AVCodec来初始化AVCodecContext结构体。在使用这个功能之前,AVCodecContext必须通过 * avcodec_alloc_context3()分配空间。 * 这些方法 avcodec_find_decoder_by_name(),avcodec_find_encoder_by_name()... 提供一个简单的方法来检索到一个编解码器。 *这个方法不是线程安全的 */int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

av_image_alloc

 /* 通过参数w,h,pix_fmt,align来分配一个图像的空间,参数pointers和linesizes将会被赋值。 * pointers必须通过av_freep(&pointers[0])释放。 * @param 调整缓冲区大小对齐的值 */int av_image_alloc(uint8_t *pointers[4], int linesizes[4],                   int w, int h, enum AVPixelFormat pix_fmt, int align);

av_read_frame

/* 返回流的下一帧到AVPacket中。 * 这个方法返回存储在文件中的数据,它并不确定这些有效帧有解码器。 * 每次调用这个方法,它将分割文件中的数据到AVPacket里。它不会遗漏 * 有效帧之间的数据,来给解码器最大的信息来解码。 * * 如果返回的AVPacket有效,在不使用时,必须用av_packet_unref释放。 * 对于视频,AVPacket包含一帧。对于音频,如果每一帧大小固定已知,那么 * AVPacket包含多帧;如果每一帧大小可变,那么AVPacket包含一帧。 * * 这几个值(pkt->pts, pkt->dts and pkt->duration)总是会被赋值。 */int av_read_frame(AVFormatContext *s, AVPacket *pkt);

struct AVPacket

 /* 这个结构体存储压缩数据。它通过解析器输出,作为解码器的输入,或者通过编码器输出, 然后传给合成器。 * 对于视频,它代表一个压缩帧,对于音频,它可能代表多个压缩帧,解码器允许输出一个空的packet, * 没有压缩数据,仅仅包含次要数据 */typedef struct AVPacket 

avcodec_send_packet

 /* 将数据包发送给解码器。(可以想象解码内部有个存放数据的容器) */int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

avcodec_receive_frame

/* 返回解码过后的一个帧,与avcodec_send_packet配套使用。 * avcodec_send_packet传入的AVPacket可能包含多帧,所以avcodec_receive_frame可以调用 * 多次,直到失败。 */int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

源码

解析MP4文件得到视频帧和音频帧,并分别保存到不同的文件。(代码许多地方返回值未校验)
extern "C"{#include <libavutil/imgutils.h>#include <libavutil/samplefmt.h>#include <libavutil/timestamp.h>#include <libavformat/avformat.h>}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 demuxing_decoding(const char* filename){av_register_all();puts("注册...");AVFormatContext* formatContext = nullptr;if (0 != avformat_open_input(&formatContext, filename, NULL, NULL)){puts("avformat_open_input失败.");return;}puts("avformat_open_input...");if (avformat_find_stream_info(formatContext, NULL) < 0){puts("avformat_find_stream_info失败.");return;}puts("avformat_find_stream_info...");//视频int video_stream_idx = av_find_best_stream(formatContext, AVMEDIA_TYPE_VIDEO, 0, -1, nullptr, 0);if (video_stream_idx < 0){puts("avformat_find_stream_info AVMEDIA_TYPE_VIDEO失败.");return;}AVStream* video_st = formatContext->streams[video_stream_idx];AVCodec *videCodec = avcodec_find_decoder(video_st->codecpar->codec_id);AVCodecContext* videoContext = avcodec_alloc_context3(videCodec);avcodec_parameters_to_context(videoContext, video_st->codecpar);avcodec_open2(videoContext, videCodec, nullptr);uint8_t *video_dst_data[4] = { NULL };int video_dst_linesize[4];int video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize, videoContext->width, videoContext->height, videoContext->pix_fmt, 1);//音频int audio_stream_idx = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);AVStream* audio_st = formatContext->streams[audio_stream_idx];AVCodec* audioCodec = avcodec_find_decoder(audio_st->codecpar->codec_id);AVCodecContext* audioContext = avcodec_alloc_context3(audioCodec);avcodec_parameters_to_context(audioContext, audio_st->codecpar);avcodec_open2(audioContext, audioCodec, nullptr);av_dump_format(formatContext, 0, filename, 0);AVFrame* frame = av_frame_alloc();AVPacket pkt;av_init_packet(&pkt);pkt.data = NULL;pkt.size = 0;FILE *videofile = nullptr;char video_name[128] = { 0 };sprintf_s(video_name, 128, "output\\demuxing_video.%s", videCodec->name);fopen_s(&videofile, video_name, "wb");FILE *audiofile = nullptr;char audio_name[128] = { 0 };sprintf_s(audio_name, 128, "output\\demuxing_audio.%s", audioCodec->name);fopen_s(&audiofile, audio_name, "wb");while (av_read_frame(formatContext, &pkt) >= 0) {if (pkt.stream_index == video_stream_idx){avcodec_send_packet(videoContext, &pkt);while (0 == avcodec_receive_frame(videoContext, frame)){av_image_copy(video_dst_data, video_dst_linesize, (const uint8_t **)frame->data, frame->linesize, videoContext->pix_fmt, videoContext->width, videoContext->height);fwrite(video_dst_data[0], 1, video_dst_bufsize, videofile);puts("写入视频");}}else if (pkt.stream_index == audio_stream_idx){avcodec_send_packet(audioContext, &pkt);while (0 == avcodec_receive_frame(audioContext, frame)){size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)frame->format);fwrite(frame->extended_data[0], 1, unpadded_linesize, audiofile);puts("写入音频");}}av_packet_unref(&pkt);}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(videoContext->pix_fmt), videoContext->width, videoContext->height,video_name);enum AVSampleFormat sfmt = audioContext->sample_fmt;int n_channels = audioContext->channels;const char *fmt;if (av_sample_fmt_is_planar(sfmt)) {const char *packed = av_get_sample_fmt_name(sfmt);printf("Warning: the sample format the decoder produced is planar ""(%s). This example will output the first channel only.\n",packed ? packed : "?");sfmt = av_get_packed_sample_fmt(sfmt);n_channels = 1;}if (get_format_from_sample_fmt(&fmt, sfmt) < 0){}printf("Play the output audio file with the command:\n""ffplay -f %s -ac %d -ar %d %s\n",fmt, n_channels, audioContext->sample_rate,audio_name);fclose(videofile);fclose(audiofile);avcodec_free_context(&videoContext);avcodec_free_context(&audioContext);avformat_close_input(&formatContext);av_frame_free(&frame);av_free(video_dst_data[0]);getchar();}








0 0