本文作为FFmpeg 解码实战首篇,我们先来实现一个解复用功能, 也就是解码并分离视频文件中的音频流和视频流,并且将视频流 和 音频 流分别保存成不同的的文件
本文链接:《【FFmpeg解码实战】(1)解码并分离视频文件中的音频流和视频流(C)》
代码流程如下:
打开文件,分配AVFormatContext 结构体上下文查找文件对应的流信息打印流信息视频解码器初始化 4.1 获取视频对应的stream_index 4.2 获取到stream 数据 4.3 根据 codec_id 查找解码器 4.4 初始化解码器上下文信息 4.5 复制 codec 相关参数到解码器上下文中 4.6 初始化并打开解码器音频解码器初始化 5.1 获取音频对应的stream_index 5.2 获取到stream 数据 5.3 根据 codec_id 查找解码器 5.4 初始化解码器上下文信息 5.5 复制 codec 相关参数到解码器上下文中 5.6 初化并打开音频解码器配置视频解复用后要保存的位置计算视频数据大小分配并初始化 AVFrame、AVPacket循环读取 一帧数据视频数据解码 10.1 将 packet 数据 发送给解码器 10.2 获取解码后的帧数据 10.3 保存帧数据到视频文件中 10.4 写入文件音频数据解码 11.1 将 packet 数据 发送给解码器 11.2 获取解码后的帧数据 11.3 写入文件发送一个空包,刷新解码器解复用完毕 #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/imgutils.h> #include <libavutil/timestamp.h> //av_ts2timestr #include <libavutil/samplefmt.h> 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; } // 参考:ffplay.c、demuxing_decoding.c int main(int argc, char* argv[]) { int ret = 0; //printf("%s \n",avcodec_configuration()); // 定义文件名 unsigned char input_filename[] = "Video_Test.mp4"; unsigned char video_dst_filename[]= "Video_Test_out.h264"; unsigned char audio_dst_filename[]= "Video_Test_out.aac"; // 1. 打开文件,分配AVFormatContext 结构体上下文 AVFormatContext* fmt_ctx = NULL; // 定义音视频格式上下文结构体 if (avformat_open_input(&fmt_ctx, input_filename, NULL, NULL) < 0 ){ printf("Could not open source file %s\n", input_filename); return 0; } // 2. 查找文件对应的流信息 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { printf("Could not find stream information\n"); return 0; } // 3. 打印流信息 av_dump_format(fmt_ctx, 0, input_filename, 0); // 4. 视频解码器初始化 // 4.1 获取视频对应的stream_index int video_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO,-1, -1, NULL, 0); printf("\n#===> Find video_stream_idx = %d\n", video_stream_idx); // 4.2 获取到stream 数据 AVStream* video_st = fmt_ctx->streams[video_stream_idx]; // 4.3 根据 codec_id 查找解码器 AVCodec* video_dec = avcodec_find_decoder(video_st->codecpar->codec_id); printf("\n#===> Find decoder: %s, coded_id:%d long name: %s\n", video_dec->name, video_dec->id, video_dec->long_name); // 4.4 初始化解码器上下文信息 AVCodecContext* video_dec_ctx = avcodec_alloc_context3(video_dec); // 4.5 复制 codec 相关参数到解码器上下文中 avcodec_parameters_to_context(video_dec_ctx, video_st->codecpar); // 4.6 初始化并打开解码器 AVDictionary* video_opts=NULL; avcodec_open2(video_dec_ctx, video_dec, &video_opts); // 5. 音频解码器初始化 // 5.1 获取音频对应的stream_index int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); printf("\n#===> Find audio_stream_idx = %d\n", audio_stream_idx); // 5.2 获取到stream 数据 AVStream* audio_st = fmt_ctx->streams[audio_stream_idx]; // 5.3 根据 codec_id 查找解码器 AVCodec* audio_dec = avcodec_find_decoder(audio_st->codecpar->codec_id); printf("\n#===> Find decoder: %s, coded_id:%d long name: %s\n", audio_dec->name, audio_dec->id, audio_dec->long_name); // 5.4 初始化解码器上下文信息 AVCodecContext* audio_dec_ctx = avcodec_alloc_context3(audio_dec); // 5.5 复制 codec 相关参数到解码器上下文中 avcodec_parameters_to_context(audio_dec_ctx, audio_st->codecpar); // 5.6 初化并打开音频解码器 AVDictionary* audio_opts = NULL; avcodec_open2(audio_dec_ctx, audio_dec, &audio_opts); // 6. 配置视频解复用后要保存的位置 //sprintf_s(video_dst_filename, "%s.%s", input_filename, video_dec->name); //sprintf_s(audio_dst_filename, "%s.%s", input_filename, audio_dec->name); FILE* video_dst_file=NULL, *audio_dst_file=NULL; ret = fopen_s(&video_dst_file, video_dst_filename, "wb"); printf("open file:%s ret:%d\n", video_dst_filename, ret); ret = fopen_s(&audio_dst_file, audio_dst_filename, "wb"); printf("open file:%s ret:%d\n", audio_dst_filename, ret); uint8_t* video_dst_data[4] = { NULL }; int video_dst_linesize[4] = {0}; // 7. 计算视频数据大小 size_t video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize, video_dec_ctx->width, video_dec_ctx->height, video_dec_ctx->pix_fmt, 1); // 8. 分配并初始化 AVFrame、AVPacket AVFrame* frame = av_frame_alloc(); AVPacket pkt; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; int video_frame_count = 0, audio_frame_count = 0; printf("Start read frame\n"); // 9. 循环读取 一帧数据 while (av_read_frame(fmt_ctx, &pkt) >= 0) { // 10. 视频数据解码 if (pkt.stream_index == video_stream_idx) { // 10.1 将 packet 数据 发送给解码器 ret = avcodec_send_packet(video_dec_ctx, &pkt); // 10.2 获取解码后的帧数据 while (ret >= 0) { ret = avcodec_receive_frame(video_dec_ctx, frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { ret = 0; break; } // 10.3 保存帧数据到视频文件中 //printf("#===> video_frame n:%d coded_n:%d ", video_frame_count++, frame->coded_picture_number); 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); // 10.4 写入文件 ret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file); //printf("Write Size:%d\n", ret); } } // 11. 音频数据解码 else if (pkt.stream_index == audio_stream_idx) { // 11.1 将 packet 数据 发送给解码器 ret = avcodec_send_packet(audio_dec_ctx, &pkt); // 11.2 获取解码后的帧数据 while (ret >= 0) { ret = avcodec_receive_frame(audio_dec_ctx, frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { ret = 0; break; } // 11.3 写入文件 //printf("#===> audio_frame n:%d nb_samples:%d pts:%s ", // audio_frame_count++, frame->nb_samples, av_ts2timestr(frame->pts, &audio_dec_ctx->time_base)); ret = (int)fwrite(frame->extended_data[0], 1, frame->nb_samples * av_get_bytes_per_sample(frame->format), audio_dst_file); //printf("Write Size:%d\n", ret); } } av_frame_unref(frame); // 清空AVPacket结构体数据 av_packet_unref(&pkt); if (ret < 0) break; } // 12.发送一个空包,刷新解码器 ret = avcodec_send_packet(video_dec_ctx, NULL); // 12.1 获取解码后的帧数据 while (ret >= 0) { ret = avcodec_receive_frame(video_dec_ctx, frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { ret = 0; break; } // 10.3 保存帧数据到视频文件中 printf("#===> video_frame n:%d coded_n:%d ", video_frame_count++, frame->coded_picture_number); 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); // 10.4 写入文件 ret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file); printf("Write Size:%d\n", ret); } ret = avcodec_send_packet(audio_dec_ctx, NULL); // 12.2 获取解码后的帧数据 while (ret >= 0) { ret = avcodec_receive_frame(audio_dec_ctx, frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { ret = 0; break; } // 11.3 写入文件 printf("#===> audio_frame n:%d nb_samples:%d pts:%s ", audio_frame_count++, frame->nb_samples, av_ts2timestr(frame->pts, &audio_dec_ctx->time_base)); ret = (int)fwrite(frame->extended_data[0], 1, frame->nb_samples * av_get_bytes_per_sample(frame->format), audio_dst_file); printf("Write Size:%d\n", ret); } // 13. 解复用完毕 printf("Demuxing succeeded.\n"); 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); 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)) { 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 ((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); fclose(video_dst_file); fclose(audio_dst_file); av_frame_free(&frame); av_free(video_dst_data[0]); return 0; }从下图可以看出 源文件《Video_Test.mp4》大小为 21.2M 解复用后的aac音频文件《Video_Test_out.aac》 大小为 27.9M 解复用后的h264视频文件《Video_Test_out.h264》 大小为 5.33G
由此可以看出音视频压缩的重要性,直接从 5.33G压缩到了 21.2M,压缩比惊人!!!
在扩展中选择安装 UTF-8,注意下载后,重启生效。
下载完后,关闭所有 vs2019窗口,开始安装UTF8扩展:
注意,配置后需要重启电脑生效。
FFmpeg windows lib库 和 DLL 下 载: 《https://github.com/BtbN/FFmpeg-Builds/releases》 《ffmpeg-N-99449-ga191d4166f-win64-gpl-shared-vulkan.zip》
《Visual Studio 2019修改编码UTF-8》