本篇文章,输入一个视频文件,分离出解码后的音视频数据,然后分别写入文件当中。
入口是:demux_decode函数。
下一篇文章会对以下代码中的一些关键函数做以说明。
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
static const char *src_filename = "/Users/zhw/Desktop/resource/sintel_h264_aac.flv";
static const char *video_dst_filename = "/Users/zhw/Desktop/sintel.yuv";
static const char *audio_dst_filename = "/Users/zhw/Desktop/sintel.pcm";
static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL, *audio_dec_ctx = NULL;
static AVStream *video_stream = NULL, *audio_stream = NULL;
static int video_stream_idx = -1, audio_stream_idx = -1;
static FILE *video_dst_file = NULL;
static FILE *audio_dst_file = NULL;
static int width, height;
static enum AVPixelFormat pix_fmt;
static AVFrame *frame = NULL;
static AVPacket pkt;
static int video_frame_count = 0;
static int audio_frame_count = 0;
static uint8_t *video_dst_data[4] = {NULL};
static int video_dst_linesize[4];
static int video_dst_bufsize;
static void demux_decode(void)
{
int ret = 0, got_frame;
/* 打开文件,创建AVFormatContext */
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
printf("Could not open source file %s\n", src_filename);
goto end;
}
/* 查找流信息 */
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
printf("Could not find stream information\n");
goto end;
}
//打开视频解码器
if (open_codec_context(&video_stream_idx, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
//视频流
video_stream = fmt_ctx->streams[video_stream_idx];
//要写入的目标视频文件
video_dst_file = fopen(video_dst_filename, "wb");
if (!video_dst_file) {
printf("could not open file %s\n", video_dst_filename);
goto end;
}
//创建用于存放解码视频帧的image
width = video_dec_ctx->width;
height = video_dec_ctx->height;
pix_fmt = video_dec_ctx->pix_fmt;
ret = av_image_alloc(video_dst_data, video_dst_linesize, width, height, pix_fmt, 1);
if (ret < 0) {
printf("Could not allocate raw video buffer\n");
goto end;
}
video_dst_bufsize = ret;
}
//打开音频解码器
if (open_codec_context(&audio_stream_idx, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
audio_stream = fmt_ctx->streams[audio_stream_idx];
audio_dst_file = fopen(audio_dst_filename, "wb");
if (!audio_dst_file) {
printf("could not open file %s\n", audio_dst_filename);
goto end;
}
}
//打印信息
av_dump_format(fmt_ctx, 0, src_filename, 0);
if (!audio_stream && !video_stream) {
printf("Could not find audio or video stream in the input, aborting\n");
goto end;
}
frame = av_frame_alloc();
if (!frame) {
printf("Could not allocate frame\n");
goto end;
}
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);
//从文件读取数据
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
/*
因为每次解码,会造成pkt的data指针移动,而丢失原始指针指向的数据,故而无法正确释放pkt.data所指向的内存。
所以用orig_pkt来保存原始指针。
*/
AVPacket orig_pkt = pkt;
do {
ret = decode_packet(&got_frame, 0);
if (ret < 0)
break;
pkt.data += ret;
pkt.size -= ret;
} while (pkt.size > 0);
av_packet_unref(&orig_pkt);
}
/* flush cached frames
将pkt.data置为null,并且pkt.size设为0,然后送给解码器解码,会将解码器缓存的数据解码出来。
*/
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 -pixel_format %s -video_size %dx%d %s\n",
av_get_pix_fmt_name(pix_fmt), width, height,
video_dst_filename);
}
if (audio_stream) {
enum AVSampleFormat sfmt = audio_dec_ctx->sample_fmt;
int n_channels = audio_dec_ctx->channels;
const char *fmt;
if (av_sample_fmt_is_planar(sfmt)) {
sfmt = av_get_packed_sample_fmt(sfmt);
}
if ((ret = get_format_from_sample_fmt(&fmt, sfmt)) < 0)
goto end;
printf("Play the output audio file with the command:\n"
"ffplay -f %s -ac %d -ar %d %s\n",
fmt, n_channels, audio_dec_ctx->sample_rate,
audio_dst_filename);
}
end:
avcodec_free_context(&video_dec_ctx);
avcodec_free_context(&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_frame_free(&frame);
av_free(video_dst_data[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;
}
static int open_codec_context(int *stream_idx, AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{
int ret, stream_index;
AVStream *st;
AVCodec *dec = NULL;
//寻找type类型的流,返回流的index
ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
if (ret < 0) {
printf("Could not find %s stream\n", av_get_media_type_string(type));
return ret;
}
stream_index = ret;
//获取流
st = fmt_ctx->streams[stream_index];
//寻找解码器
dec = avcodec_find_decoder(st->codecpar->codec_id);
if (!dec) {
printf("failed to find %s codec\n", av_get_media_type_string(type));
return AVERROR(EINVAL);
}
//为解码器创建上下文
*dec_ctx = avcodec_alloc_context3(dec);
if (!*dec_ctx) {
printf("failed to alloc %s codec context\n", av_get_media_type_string(type));
return AVERROR(ENOMEM);
}
//把流中的解码参数复制到解码器的AVCodecContext中
ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar);
if (ret < 0) {
printf("failed to copy %s codec params to decoder context\n", av_get_media_type_string(type));
return ret;
}
//打开解码器
ret = avcodec_open2(*dec_ctx, dec, NULL);
if (ret < 0) {
printf("failed to open %s codec\n", av_get_media_type_string(type));
return ret;
}
//返回流的index
*stream_idx = stream_index;
return 0;
}
static int decode_packet(int *got_frame, int cached)
{
int ret = 0;
int decoded = pkt.size;
*got_frame = 0;
if (pkt.stream_index == video_stream_idx) {
//解码视频
ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
printf("error decode video\n");
return ret;
}
if (*got_frame) {
if (frame->width != width || frame->height != height || frame->format != pix_fmt) {
printf("Error: Width, height and pixel format have to be "
"constant in a rawvideo file, but the width, height or "
"pixel format of the input video changed");
return -1;
}
printf("video_frame%s n:%d coded_n:%d\n",
cached ? "(cached)" : "",
video_frame_count++, frame->coded_picture_number);
//复制解码后的视频到之前创建的缓存区
av_image_copy(video_dst_data, video_dst_linesize, (const uint8_t **)frame->data, frame->linesize, pix_fmt, width, height);
//写入视频
fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
}
}else if (pkt.stream_index == audio_stream_idx) {
ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
if (ret < 0) {
printf("error decode audio\n");
return ret;
}
/*
一些音频解码器一次只能解码pkt的一部分,所以必须再次调用来解码pkt的剩余部分。
而另一些解码器可能会读取超过pkt.size字节的数据,造成ret大于pkt.size。
*/
decoded = FFMIN(ret, pkt.size);
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_sample_fmt_is_planar(frame->format);
if (ret == 1) { //planar
int nb_samples = frame->nb_samples;
int bytes_per_sample = av_get_bytes_per_sample(frame->format);
int channels = frame->channels;
int pos = 0;
//存放packed的音频
int buf_size = nb_samples * bytes_per_sample * channels;
uint8_t *audio_buffer = calloc(buf_size, sizeof(uint8_t));
if (!audio_buffer) {
printf("alloc audio memory fail\n");
return -1;
}
for (int i = 0; i < nb_samples; i++) {
for (int j = 0; j < channels; j++) {
memcpy(audio_buffer+pos, frame->extended_data[j] + i * bytes_per_sample, bytes_per_sample);
pos += bytes_per_sample;
}
}
fwrite(audio_buffer, 1, buf_size, audio_dst_file);
free(audio_buffer);
}else {//packed
fwrite(frame->extended_data[0], 1, frame->linesize[0], audio_dst_file);
}
}
}
return decoded;
}