FFmpeg 和 SDL 教程 Part 1 - 分离音、视频数据
发布日期:2021-06-29 14:55:27 浏览次数:3 分类:技术文章

本文共 16115 字,大约阅读时间需要 53 分钟。

FFmpeg 和 SDL 教程 Part 1 - 分离音、视频数据

一、分离音、视频数据

视频文件拥有一些基本的组件,

  1. Container容器

    文件本身是一个container容器,容器的类型决定文件的信息类型,如 AVI 和 Quicktime。

  2. Streams流

    文件中有一堆的streams流信息,比如:音频流 audio stream 和 视频流 video stream。
    stream 流是一个会随着时间推进的一系列数据,stream 流中的数据叫作 frames 帧,
    每一个stream 流都是被不同的codec编解码器,codec编解码器决定了数据如何进行编码或者解码。如DivX 和 MP3。

  3. Packets包

    Packets包是从stream流中读取出来的,Packets包中的数据经过解码后就是原始帧信息raw frames,最终应用程序中使用的就是这些原始帧信息 raw frames。一个packets包中可以包含一个完整的视频帧,也可以同时包含多个音频帧。

处理音视频流非常简单,总结如下:

  1. 从 video.avi中打开 video_stream。
  2. 从video_stream 中读取packet 包,并解码为相应的frame 帧
  3. 如果读取到的数据不是完整帧,则重复第2步继续读取packet包并解码帧。
  4. 显示完整帧或做其他的操作。
  5. 重复第二步继续读取下一个packet包。

接下来,本文将实现打开一个视频文件,读取视频流,将这些视频流解码成frame 帧后写入 PPM文件中。

1.1 打开文件

使用 FFmepg 前,需要包含它的头文件,通过使用avcodec_version() 确认FFmepg相关的头文件,函数库是否正确包含。

#include 
extern "C" {
#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"}int main(int argc, char* argv[]) {
printf("avcodec version: 0x%x\n", avcodec_version()); return 0;}

现在,通过 avformat_open_input() 来打开视频文件,该函数是用于读取文件的头信息

AVFormatContext* pFormatCtx = NULL;	pFormatCtx = avformat_alloc_context();	// 初始化 Data fields 上下文	// 1. 打开文件, 读取文件头信息, 并写入AVFormatContext中	if (avformat_open_input(&pFormatCtx, Video_FileName, NULL, NULL) != 0) {
p_err("avformat_open_input %s Failed !!!\n", Video_FileName); return -1; }

查找文件中的 stream information,将所有流信息保存在 pFormatCtx->streams[]数组中,

数组的大小为 pFormatCtx->nb_streams(stream流种类数量)

// 2. 查找文件中的 stream information	// 将所有的流信息保存在 pFormatCtx->streams[]数组中,数组的大小为 pFormatCtx->nb_streams(stream流种类数量)	if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
p_err("avformat_find_stream_info error !!!\n"); return -1; }

打印找到的流信息

// 3. 打印找到的流信息	av_dump_format(pFormatCtx, 0, Video_FileName, 0);

找到对应的视频流信息、音频流信息,相关的stream index 保存在 st_index[type] 中,

接着通过codec_id 找到对应的视频解码器,初始化并打开解码器。

// 4. 找到对应的视频流信息、音频流信息,及初始化对应的codec编解码器	memset(st_index, -1, AVMEDIA_TYPE_NB);	for (int i = 0; i < pFormatCtx->nb_streams; i++) {
AVMediaType type = pFormatCtx->streams[i]->codecpar->codec_type; switch (type) {
case AVMEDIA_TYPE_VIDEO: // 视频流 case AVMEDIA_TYPE_AUDIO: // 音频流 st_index[type] = i; p_info("Find %s Stream: %d \n", (type==AVMEDIA_TYPE_VIDEO?"Video":"Audio"), st_index[AVMEDIA_TYPE_VIDEO]); // 开始初始化 video codec // (1) 获得视频流 p_Stream[type] = pFormatCtx->streams[i]; // (2) 找到视频解码器 p_Codec[type] = avcodec_find_decoder(p_Stream[type]->codecpar->codec_id); if (p_Codec[type] != NULL) {
// (3) 初始化解码器上下文 p_Codec_Ctx[type] = avcodec_alloc_context3(p_Codec[type]); // (4) 拷贝解码器信息到上下文 avcodec_parameters_to_context(p_Codec_Ctx[type], p_Stream[type]->codecpar); // (5) 打开视频解码器 if (avcodec_open2(p_Codec_Ctx[type], p_Codec[type], NULL) >= 0) {
p_info("Open Codec %s Success\n", p_Codec[type]->long_name); } else {
p_err("Open Codec %s Failed\n", p_Codec[type]->long_name); } } else {
p_err("Find Codec id %d Failed\n", p_Stream[type]->codecpar->codec_id); } break; case AVMEDIA_TYPE_SUBTITLE: st_index[type] = i; p_info("Find Subtitle Stream: %d \n", st_index[AVMEDIA_TYPE_SUBTITLE]); break; default: p_err("Default: pFormatCtx->streams[%d]->codec->codec_type = %d\n", i, type); break; } }

1.2 分配 avframe 和 avpacket 内存

// 5. 分配 avframe 和 avpacket 内存	p_Frame = av_frame_alloc();	p_Frame_yuv = av_frame_alloc();	// 计算图片大小	int pic_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, 1);	// 申请图片内存	uint8_t* pic_buffer = (uint8_t*)av_malloc(pic_size * sizeof(uint8_t));	// 将p_Frame_yuv 与所申请的buffer 绑定在一起	av_image_fill_arrays(p_Frame_yuv->data, p_Frame_yuv->linesize, pic_buffer, AV_PIX_FMT_YUV420P, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, 1);	// 创建图片转换上下文	p_Video_Sws_Ctx = sws_getContext(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt,		p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

1.3 开始循环读取数据

// 6. 开始循环读取视频数据	AVPacket pkt;	int ret;#ifdef Save_Video_File	FILE* video_dst_file;	char video_dst_filename[50];	memset(video_dst_filename, '\0', 50);	sprintf_s(video_dst_filename, 50, "%s_video.%s", Video_FileName, p_Codec[AVMEDIA_TYPE_VIDEO]->name);	fopen_s(&video_dst_file, video_dst_filename, "wb");	uint8_t* video_dst_data[4] = {
NULL }; int video_dst_linesize[4] = {
0 }; size_t video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt, 1);#endif#ifdef Save_Audio_File FILE* audio_dst_file; char audio_dst_filename[50]; memset(audio_dst_filename, '\0', 50); sprintf_s(audio_dst_filename, 50, "%s_audio.%s", Video_FileName, p_Codec[AVMEDIA_TYPE_AUDIO]->name); fopen_s(&audio_dst_file, audio_dst_filename, "wb");#endif while (av_read_frame(pFormatCtx, &pkt) >= 0) {
if (pkt.stream_index == st_index[AVMEDIA_TYPE_VIDEO]) {
// 送数据进入解码器 ret = avcodec_send_packet(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO], &pkt); if (ret < 0) p_err("avcodec_send_packet video Failed\n"); while (ret >= 0) {
// 获取解码后的帧数据 ret = avcodec_receive_frame(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO], p_Frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
break; }#ifdef Save_Video_File av_image_copy(video_dst_data, video_dst_linesize, (const uint8_t**)(p_Frame->data), p_Frame->linesize, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height); ret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file); p_info("视频:第%5d帧,pts=%ld\n", video_cnt++, p_Frame->pts);#endif } } else if(pkt.stream_index == st_index[AVMEDIA_TYPE_AUDIO]){
ret = avcodec_send_packet(p_Codec_Ctx[AVMEDIA_TYPE_AUDIO], &pkt); if (ret < 0) p_err("avcodec_send_packet audio Failed\n"); while (ret >= 0) {
ret = avcodec_receive_frame(p_Codec_Ctx[AVMEDIA_TYPE_AUDIO], p_Frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
break; }#ifdef Save_Audio_File ret = fwrite(p_Frame->extended_data[0], 1, p_Frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)p_Frame->format), audio_dst_file);#endif p_info("音频:第%5d帧,fmt=%d, srate=%d\n", audio_cnt++, p_Frame->format, p_Frame->sample_rate); } } else if (pkt.stream_index == st_index[AVMEDIA_TYPE_SUBTITLE]) {
} av_frame_unref(p_Frame); }

1.4 完整代码

#include 
extern "C" {
#include "libavcodec/avcodec.h"#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include "libavutil/imgutils.h"}#define ENABLE_DEBUG#define Save_Video_File // ffplay -f rawvideo -pix_fmt yuv420p -video_size 1050x540 video.mp4_video.h264#define Save_Audio_File // ffplay -f f32le -ac 1 -ar 44100 video.mp4_audio.aac#ifdef ENABLE_DEBUG#define p_err(...) printf(__VA_ARGS__)#define p_info(...) printf(__VA_ARGS__)#else#define p_err(...)#define p_info(...)#endifusing namespace std;const char* Video_FileName = "video.mp4";AVFormatContext* pFormatCtx = NULL; // 文件上下文int st_index[AVMEDIA_TYPE_NB]; // 定义一个数组保存所有的流信息AVStream* p_Stream[AVMEDIA_TYPE_NB]; // 定义AVStream 数组AVCodec* p_Codec[AVMEDIA_TYPE_NB]; // 定义AVCodec 数组AVCodecContext* p_Codec_Ctx[AVMEDIA_TYPE_NB]; // 定义AVCodec 数组int64_t video_cnt=0, audio_cnt=0;struct SwsContext* p_Video_Sws_Ctx=NULL; // 图片格式转换上下文AVFrame* p_Frame = NULL, * p_Frame_yuv = NULL; int main(int argc, char* argv[]) {
p_info("avcodec version: 0x%x\n", avcodec_version()); pFormatCtx = avformat_alloc_context(); // 初始化 Data fields 上下文 // 1. 打开文件, 读取文件头信息, 并写入AVFormatContext中 if (avformat_open_input(&pFormatCtx, Video_FileName, NULL, NULL) != 0) {
p_err("avformat_open_input %s Failed !!!\n", Video_FileName); return -1; } // 2. 查找文件中的 stream information // 将所有的流信息保存在 pFormatCtx->streams[]数组中,数组的大小为 pFormatCtx->nb_streams(stream流种类数量) if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
p_err("avformat_find_stream_info error !!!\n"); return -1; } // 3. 打印找到的流信息 av_dump_format(pFormatCtx, 0, Video_FileName, 0); // 4. 找到对应的视频流信息、音频流信息,及初始化对应的codec编解码器 memset(st_index, -1, AVMEDIA_TYPE_NB); for (int i = 0; i < pFormatCtx->nb_streams; i++) {
AVMediaType type = pFormatCtx->streams[i]->codecpar->codec_type; switch (type) {
case AVMEDIA_TYPE_VIDEO: // 视频流 case AVMEDIA_TYPE_AUDIO: // 音频流 st_index[type] = i; p_info("Find %s Stream: %d \n", (type==AVMEDIA_TYPE_VIDEO?"Video":"Audio"), st_index[AVMEDIA_TYPE_VIDEO]); // 开始初始化 video codec // (1) 获得视频流 p_Stream[type] = pFormatCtx->streams[i]; // (2) 找到视频解码器 p_Codec[type] = avcodec_find_decoder(p_Stream[type]->codecpar->codec_id); if (p_Codec[type] != NULL) {
// (3) 初始化解码器上下文 p_Codec_Ctx[type] = avcodec_alloc_context3(p_Codec[type]); // (4) 拷贝解码器信息到上下文 avcodec_parameters_to_context(p_Codec_Ctx[type], p_Stream[type]->codecpar); // (5) 打开视频解码器 if (avcodec_open2(p_Codec_Ctx[type], p_Codec[type], NULL) >= 0) {
p_info("Open Codec %s Success\n", p_Codec[type]->long_name); } else {
p_err("Open Codec %s Failed\n", p_Codec[type]->long_name); } } else {
p_err("Find Codec id %d Failed\n", p_Stream[type]->codecpar->codec_id); } break; case AVMEDIA_TYPE_SUBTITLE: st_index[type] = i; p_info("Find Subtitle Stream: %d \n", st_index[AVMEDIA_TYPE_SUBTITLE]); break; default: p_err("Default: pFormatCtx->streams[%d]->codec->codec_type = %d\n", i, type); break; } } // 5. 分配 avframe 和 avpacket 内存 p_Frame = av_frame_alloc(); p_Frame_yuv = av_frame_alloc(); // 计算图片大小 int pic_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, 1); // 申请图片内存 uint8_t* pic_buffer = (uint8_t*)av_malloc(pic_size * sizeof(uint8_t)); // 将p_Frame_yuv 与所申请的buffer 绑定在一起 av_image_fill_arrays(p_Frame_yuv->data, p_Frame_yuv->linesize, pic_buffer, AV_PIX_FMT_YUV420P, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, 1); // 创建图片转换上下文 p_Video_Sws_Ctx = sws_getContext(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); // 6. 开始循环读取视频数据 AVPacket pkt; int ret;#ifdef Save_Video_File FILE* video_dst_file; char video_dst_filename[50]; memset(video_dst_filename, '\0', 50); sprintf_s(video_dst_filename, 50, "%s_video.%s", Video_FileName, p_Codec[AVMEDIA_TYPE_VIDEO]->name); fopen_s(&video_dst_file, video_dst_filename, "wb"); uint8_t* video_dst_data[4] = {
NULL }; int video_dst_linesize[4] = {
0 }; size_t video_dst_bufsize = av_image_alloc(video_dst_data, video_dst_linesize, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt, 1);#endif#ifdef Save_Audio_File FILE* audio_dst_file; char audio_dst_filename[50]; memset(audio_dst_filename, '\0', 50); sprintf_s(audio_dst_filename, 50, "%s_audio.%s", Video_FileName, p_Codec[AVMEDIA_TYPE_AUDIO]->name); fopen_s(&audio_dst_file, audio_dst_filename, "wb");#endif while (av_read_frame(pFormatCtx, &pkt) >= 0) {
if (pkt.stream_index == st_index[AVMEDIA_TYPE_VIDEO]) {
// 送数据进入解码器 ret = avcodec_send_packet(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO], &pkt); if (ret < 0) p_err("avcodec_send_packet video Failed\n"); while (ret >= 0) {
// 获取解码后的帧数据 ret = avcodec_receive_frame(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO], p_Frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
break; }#ifdef Save_Video_File av_image_copy(video_dst_data, video_dst_linesize, (const uint8_t**)(p_Frame->data), p_Frame->linesize, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height); ret = (int)fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file); p_info("视频:第%5d帧,pts=%ld\n", video_cnt++, p_Frame->pts);#endif } } else if(pkt.stream_index == st_index[AVMEDIA_TYPE_AUDIO]){
ret = avcodec_send_packet(p_Codec_Ctx[AVMEDIA_TYPE_AUDIO], &pkt); if (ret < 0) p_err("avcodec_send_packet audio Failed\n"); while (ret >= 0) {
ret = avcodec_receive_frame(p_Codec_Ctx[AVMEDIA_TYPE_AUDIO], p_Frame); if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
break; }#ifdef Save_Audio_File ret = fwrite(p_Frame->extended_data[0], 1, p_Frame->nb_samples * av_get_bytes_per_sample((AVSampleFormat)p_Frame->format), audio_dst_file);#endif p_info("音频:第%5d帧,fmt=%d, srate=%d\n", audio_cnt++, p_Frame->format, p_Frame->sample_rate); } } else if (pkt.stream_index == st_index[AVMEDIA_TYPE_SUBTITLE]) {
} av_frame_unref(p_Frame); }#ifdef Save_Video_File if (video_dst_file != NULL) fclose(video_dst_file); printf("视频播放命令:ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n", av_get_pix_fmt_name(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt), p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, video_dst_filename); // 生成bat 脚本 FILE* bat_dst_file = NULL; char bat_out[100] = ""; memset(bat_out, '\0', 100); sprintf_s(bat_out, 99, "ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s", av_get_pix_fmt_name(p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->pix_fmt), p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->width, p_Codec_Ctx[AVMEDIA_TYPE_VIDEO]->height, video_dst_filename); ret = fopen_s(&bat_dst_file, "play_video.bat", "wb"); ret = (int)fwrite(bat_out, 1, sizeof(bat_out), bat_dst_file); fclose(bat_dst_file);#endif#ifdef Save_Audio_File if (audio_dst_file != NULL) fclose(audio_dst_file); const char* Audio_format[] = {
"u8" , // AV_SAMPLE_FMT_U8, ///< unsigned 8 bits "s16be" , // AV_SAMPLE_FMT_S16, ///< signed 16 bits "s32be" , // AV_SAMPLE_FMT_S32, ///< signed 32 bits "f32be" , // AV_SAMPLE_FMT_FLT, ///< float "f64be" , // AV_SAMPLE_FMT_DBL, ///< double // "u8" , // AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar "s16le" , // AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar "s32le" , // AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar "f32le" , // AV_SAMPLE_FMT_FLTP, ///< float, planar "f64le" , // AV_SAMPLE_FMT_DBLP, ///< double, planar "none" }; enum AVSampleFormat sfmt = p_Codec_Ctx[AVMEDIA_TYPE_AUDIO]->sample_fmt; int n_channels = p_Codec_Ctx[AVMEDIA_TYPE_AUDIO]->channels; const char* fmt; if (av_sample_fmt_is_planar(sfmt)) {
const char* packed = av_get_sample_fmt_name(sfmt); //sfmt = av_get_packed_sample_fmt(sfmt); cout << "当前音频数据格式为 planar (" << (packed ? packed : "?") << "), channels配置为1, AVSampleFormat: " << Audio_format[sfmt] << endl; n_channels = 1; } printf("音频播放命令:ffplay -f %s -ac %d -ar %d %s\n", Audio_format[sfmt], n_channels, p_Codec_Ctx[AVMEDIA_TYPE_AUDIO]->sample_rate, audio_dst_filename); // 生成bat 脚本 FILE* bat_dst_file_1 = NULL; char bat_out_1[100] = ""; memset(bat_out_1, '\0', 100); sprintf_s(bat_out_1, 99, "ffplay -f %s -ac %d -ar %d %s", Audio_format[sfmt], n_channels, p_Codec_Ctx[AVMEDIA_TYPE_AUDIO]->sample_rate, audio_dst_filename); ret = fopen_s(&bat_dst_file_1, "play_audio.bat", "wb"); ret = (int)fwrite(bat_out_1, 1, sizeof(bat_out_1), bat_dst_file_1); fclose(bat_dst_file_1);#endif if (p_Video_Sws_Ctx) sws_freeContext(p_Video_Sws_Ctx); if (pic_buffer) av_free(pic_buffer); if (p_Frame) av_frame_free(&p_Frame); if (p_Frame_yuv) av_frame_free(&p_Frame_yuv); for (int i = 0; i < pFormatCtx->nb_streams; i++) {
AVMediaType type = pFormatCtx->streams[i]->codecpar->codec_type; switch (type) {
case AVMEDIA_TYPE_VIDEO: // 视频流 case AVMEDIA_TYPE_AUDIO: // 音频流 if (p_Codec_Ctx[type]) {
avcodec_close(p_Codec_Ctx[type]); avcodec_free_context(&p_Codec_Ctx[type]); } break; default: break; } } if (pFormatCtx){
avformat_close_input(&pFormatCtx); avformat_free_context(pFormatCtx); } return 0;}

英文版原文链接:

《》
《》
《》

转载地址:https://ciellee.blog.csdn.net/article/details/112303631 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:27岁,人生规划(大方向调整)(持续更新)
下一篇:【FFmpeg解码实战】(7)从零实现FFmpeg4.3 + SDL2视频播放器 - Video_Player 主类实现

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月22日 18时43分16秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章