今天学习解析媒体文件。
写了一个用例,解析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();
}