最简单的基于 FFmpeg 的视音频分离器 - 简化版

本文介绍了如何使用FFmpeg实现一个简单的视音频分离器,用于将FLV文件中的H.264视频和MP3音频分开,同时处理H.264编码可能需要的SPS和PPS附加。注意,此工具对某些封装格式如MP4/MKV存在局限性。
摘要由CSDN通过智能技术生成

最简单的基于 FFmpeg 的视音频分离器 - 简化版

参考雷霄骅博士的文章,链接:最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)

正文

本文介绍一个视音频分离器(Demuxer)。

视音频分离器即是将封装格式数据(例如 MKV)中的视频压缩数据(例如 H.264)和音频压缩数据(例如 AAC)分离开,如下图所示。

在这里插入图片描述

在这个过程中并不涉及到编码和解码。

本文记录的程序将一个 FLV 封装的文件(其中视频编码为 H.264,音频编码为 MP3)分离成为两个文件:一个 H.264 编码的视频码流文件,一个 MP3 编码的音频码流文件。

需要注意的是,本文介绍的是一个简单版的视音频分离器(Demuxer)。该分离器的优点是代码十分简单,很好理解。但是缺点是并不适用于一些格式。对于 MP3 编码的音频是没有问题的。但是在分离 MP4/FLV/MKV 等一些格式中的 AAC 编码的码流的时候,得到的 AAC 码流是不能播放的。原因是存储 AAC 数据的 AVPacket 的 data 字段中的数据是不包含 7 字节 ADTS 文件头的“砍头”的数据,是无法直接解码播放的(当然如果在这些数据前面手工加上 7 字节的 ADTS 文件头的话,就可以播放了)。

分离某些封装格式(例如 MP4/FLV/MKV 等)中的 H.264 的时候,需要首先写入 SPS 和 PPS,否则会导致分离出来的数据没有 SPS、PPS 而无法播放。H.264 码流的 SPS 和 PPS 信息存储在 AVCodecContext 结构体的 extradata 中。需要使用 FFmpeg 中名称为“h264_mp4toannexb”的 bitstream filter 处理。有两种处理方式:

(1)使用 bitstream filter 处理每个 AVPacket(简单)

把每个 AVPacket 中的数据(data 字段)经过 bitstream filter “过滤”一遍。关键函数是 av_bitstream_filter_filter()。示例代码如下:


	AVBitStreamFilterContext* h264bsfc =  av_bitstream_filter_init("h264_mp4toannexb"); 
	while(av_read_frame(ifmt_ctx, &pkt)>=0){
		if(pkt.stream_index==videoindex){
			av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
			fwrite(pkt.data,1,pkt.size,fp_video);
			//...
		}
		av_free_packet(&pkt);
	}
	av_bitstream_filter_close(h264bsfc);

上述代码中,把 av_bitstream_filter_filter() 的输入数据和输出数据(分别对应第 4,5,6,7 个参数)都设置成 AVPacket 的 data 字段就可以了。

需要注意的是 bitstream filter 需要初始化和销毁,分别通过函数 av_bitstream_filter_init() 和 av_bitstream_filter_close()。

经过上述代码处理之后,AVPacket 中的数据有如下变化:

  • 每个 AVPacket 的data 添加了 H.264 的 NALU 的起始码 {0,0,0,1}。

  • 每个 IDR 帧数据前面添加了 SPS 和 PPS。

(2)手工添加 SPS,PPS(稍微复杂)

将 AVCodecContext 的 extradata 数据经过 bitstream filter 处理之后得到 SPS、PPS,拷贝至每个 IDR 帧之前。下面代码示例了写入 SPS、PPS 的过程。

FILE *fp=fopen("test.264","ab");
AVCodecContext *pCodecCtx=...  
unsigned char *dummy=NULL;   
int dummy_len;  
AVBitStreamFilterContext* bsfc =  av_bitstream_filter_init("h264_mp4toannexb");    
av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, 0, 0);  
fwrite(pCodecCtx->extradata,pCodecCtx->extradata_size,1,fp);  
av_bitstream_filter_close(bsfc);    
free(dummy);

然后修改 AVPacket 的 data。把前 4 个字节改为起始码。示例代码如下所示:

char nal_start[]={0,0,0,1};
memcpy(packet->data,nal_start,4);

经过上述两步也可以得到可以播放的 H.264 码流,相对于第一种方法来说复杂一些。

当封装格式为 MPEG2TS 的时候,不存在上述问题。

程序的流程如下图所示:

在这里插入图片描述

从流程图中可以看出,将每个通过 av_read_frame() 获得的 AVPacket 中的数据直接写入文件即可。

简单介绍一下流程中各个重要函数的意义:

  1. avformat_open_input():打开输入文件。
  2. av_read_frame():获取一个 AVPacket。
  3. fwrite():根据得到的 AVPacket 的类型不同,分别写入到不同的文件中。

源程序:

// Simplest FFmpeg Demuxer Simple.cpp : 定义控制台应用程序的入口点。
//


/**
* 最简单的基于 FFmpeg 的视音频分离器(简化版)
* Simplest FFmpeg Demuxer Simple
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序可以将封装格式中的视频码流数据和音频码流数据分离出来。
* 在该例子中, 将FLV的文件分离得到 H.264 视频码流文件和 MP3 音频码流文件。
*
* 注意:
* 这个是简化版的视音频分离器。
* 与原版的不同在于,没有初始化输出视频流和音频流的 AVFormatContext,
* 而是直接将解码后的得到的 AVPacket 中的的数据通过 fwrite() 写入文件。
* 这样做的好处是流程比较简单。
* 坏处是对一些格式的视音频码流是不适用的,比如说 FLV/MP4/MKV 等格式中的 AAC 码流
* (上述封装格式中的 AAC 的 AVPacket 中的数据缺失了 7 字节的 ADTS 文件头)。
*
* This software split a media file (in Container such as MKV, FLV, AVI...)
* to video and audio bitstream.
* In this example, it demux a FLV file to H.264 bitstream and MP3 bitstream.
* 
* Note:
* This is a simple version of "Simplest FFmpeg Demuxer". 
* It is more simple because it doesn't init Output Video/Audio stream's AVFormatContext.
* It writes AVPacket's data to files directly.
* The advantages of this method is simple.
* The disadvantages of this method is it's not suitable for some kind of bitstreams.
* Forexample, AAC bitstream in FLV/MP4/MKV Container Format
* (data in AVPacket lack of 7 bytes of ADTS header).
*
*/

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>

// 解决报错:'fopen': This function or variable may be unsafe.Consider using fopen_s instead.
#pragma warning(disable:4996)

// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{
	// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用
	FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}

#define __STDC_CONSTANT_MACROS

#ifdef _WIN32
// Windows
extern "C"
{
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif


// 1: Use H.264 Bitstream Filter 
#define USE_H264BSF 1

int main(int argc, char* argv[])
{
	AVFormatContext *ifmt_ctx = NULL;
	AVPacket pkt;

	int ret;
	int videoindex = -1, audioindex = -1;

	// Input file URL
	const char *in_filename = "cuc_ieschool.flv";
	// Output video file URL
	const char *out_video_filename = "cuc_ieschool.h264";
	// Output audio file URL
	const char *out_audio_filename = "cuc_ieschool.mp3";

	av_register_all();

	// 输入
	ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0);
	if (ret < 0)
	{
		printf("Could not open input file.\n");
		return -1;
	}

	ret = avformat_find_stream_info(ifmt_ctx, 0);
	if (ret < 0)
	{
		printf("Failed to retrieve input stream information.\n");
		return -1;
	}

	// Print some input information
	av_dump_format(ifmt_ctx, 0, in_filename, 0);

	for (size_t i = 0; i < ifmt_ctx->nb_streams; i++)
	{
		if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
			videoindex = i;
		else if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
			audioindex = i;
	}

	FILE *fp_audio = fopen(out_audio_filename, "wb+");
	FILE *fp_video = fopen(out_video_filename, "wb+");

	/*
	FIX: H.264 in some container formats (FLV, MP4, MKV etc.)
	need "h264_mp4toannexb" bitstream filter (BSF).
	1. Add SPS,PPS in front of IDR frame
	2. Add start code ("0,0,0,1") in front of NALU
	H.264 in some containers (such as MPEG2TS) doesn't need this BSF.
	*/
#if USE_H264BSF
	AVBitStreamFilterContext* h264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
#endif

	while (1)
	{
		// 获取一个 AVPacket
		ret = av_read_frame(ifmt_ctx, &pkt);
		if (ret < 0)
		{
			break;
		}

		if (pkt.stream_index == videoindex)
		{
#if USE_H264BSF
			av_bitstream_filter_filter(h264bsfc, ifmt_ctx->streams[videoindex]->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);
#endif
			printf("Write a video packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
			fwrite(pkt.data, 1, pkt.size, fp_video);
		}
		else if (pkt.stream_index == audioindex)
		{
			/*
			AAC in some container formats (FLV, MP4, MKV etc.) need to
			add 7 Bytes ADTS Header in front of AVPacket data manually.
			Other Audio Codec (MP3...) works well.
			*/
			printf("Write a audio packet. size:%d\tpts:%lld\n", pkt.size, pkt.pts);
			fwrite(pkt.data, 1, pkt.size, fp_audio);
		}
		av_free_packet(&pkt);
	}


#if USE_H264BSF
	av_bitstream_filter_close(h264bsfc);
#endif

	fclose(fp_video);
	fclose(fp_audio);

	avformat_close_input(&ifmt_ctx);

	system("pause");
	return 0;
}

结果

运行程序,输出如下:

在这里插入图片描述

输入文件为:
cuc_ieschool.flv:FLV 封装格式数据。

在这里插入图片描述

输出文件为:

cuc_ieschool.mp3:MP3 音频码流数据。

在这里插入图片描述

cuc_ieschool.h264:H.264 视频码流数据。

在这里插入图片描述

在这里插入图片描述

工程文件下载

GitHub:UestcXiye / Simplest-FFmpeg-Demuxer-Simple

CSDN:Simplest FFmpeg Demuxer Simple.zip

参考链接

  1. 使用FFMPEG类库分离出多媒体文件中的音频码流
  2. 使用FFMPEG类库分离出多媒体文件中的H.264码流
  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要下载一个基于FFmpeg SDL的最简单的视频播放器,可以按照以下步骤进行: 1. 首先,需要下载和安装FFmpeg软件包。FFmpeg是一个开源的跨平台多媒体框架,可以用于处理音频和视频文件。可以上FFmpeg官网(https://www.ffmpeg.org/)找到相应的下载链接,并根据操作系统选择正确的版本进行下载和安装。 2. 下载SDL库。SDL是一个跨平台的开发库,可以用于创建多媒体应用程序。可以在SDL官网(https://www.libsdl.org/)上找到相应的下载链接,并选择适合自己操作系统的版本进行下载和安装。 3. 使用编程语言(如C/C++)编写一个基于FFmpeg和SDL的视频播放器。可以使用任何喜欢的集成开发环境(IDE),如Visual Studio、Dev-C++等。根据自己的需求,可以封装FFmpeg和SDL的相关函数,以方便播放视频文件。 4. 在编程中,需要包含FFmpeg和SDL所需的头文件,并链接FFmpeg和SDL的库文件。可以在编译选项中添加"-lffmpeg"和"-lsdl"等参数。 5. 编写代码来打开视频文件,读取视频流,将每一帧解码和渲染到屏幕上并进行播放。可以使用FFmpeg提供的函数来进行解码和渲染,使用SDL提供的函数来显示图像并进行窗口管理。 6. 编译和运行程序,即可实现最简单的基于FFmpeg SDL的视频播放器。可以通过命令行输入视频文件的路径进行播放。 需要注意的是,基于FFmpeg SDL的视频播放器可以根据个人需求来进行功能的扩展,如添加播放控制(播放、暂停、停止等)、全屏显示、音量调节等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值