FFmpeg进阶: 采用音频滤镜对音频进行转码


很多时候为了让视频文件适应不同的播放领域,我们需要对音频文件进行转码操作,转码操作其实主要就是修改音频文件的各种参数包括:采样位数、采样率、音频布局、码率等等。下面分别介绍一下各个参数的意义和作用。

采样位数

采样位数也称为位深度、分辨率, 它是指声音的连续强度被数字表示后可以分为多少级。N-bit的意思声音的强度被均分为2^N级。16位的就是65535级。这是一个很大的数了,人可能也分辨不出1/65536的音强差别。也可以说是声卡的分辨率,它的数值越大,分辨率也就越高,所发出声音的能力越强。这里的采样倍数主要针对的是信号的强度特性,采样率针对的是信号的时间(频率)特性这是两个不一样的概念。

ffmpeg常用的采样位数对应的格式如下所示:

enum AVSampleFormat {
    
    
	AV_SAMPLE_FMT_NONE = -1,
	AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
	AV_SAMPLE_FMT_S16,         ///< signed 16 bits
	AV_SAMPLE_FMT_S32,         ///< signed 32 bits
	AV_SAMPLE_FMT_FLT,         ///< float
	AV_SAMPLE_FMT_DBL,         ///< double

	AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
	AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
	AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
	AV_SAMPLE_FMT_FLTP,        ///< float, planar
	AV_SAMPLE_FMT_DBLP,        ///< double, planar
	AV_SAMPLE_FMT_S64,         ///< signed 64 bits
	AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

	AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

采样率

音频采样,是把声音从模拟信号转换为数字信号。采样率,就是每秒对声音进行采集的次数,同样也是所得的数字信号的每秒样本数。在对声音进行采样时,常用的采样率有:
8,000 Hz - 电话所用采样率, 对于人的说话已经足够
11,025 Hz - AM调幅广播所用采样率
22,050 Hz~24,000 Hz - FM调频广播所用采样率
32,000 Hz - miniDV 数码视频 camcorder、DAT (LP mode)所用采样率
44,100 Hz - 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率
47,250 Hz - 商用PCM录音机所用采样率
48,000 Hz - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
50,000 Hz - 商用数字录音机所用采样率
96,000 或者192,000 Hz - DVD-Audio、一些 LPCM DVD 音轨、BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨所用所用采样率
2.8224 MHz - Direct Stream Digital 的 1 位 sigma-delta modulation 过程所用采样率。

采样越高,声音的还原就越真实越自然,人对频率的识别范围是20HZ - 20000HZ, 如果每秒钟能对声音做 20000 个采样, 回放时就足可以满足人耳的需求.所以 22050 的采样频率是常用的, 44100已是CD音质, 超过48000的采样对人耳已经没有意义。这和电影的每秒 24 帧图片的道理差不多。

声道布局

当人听到声音时,能对声源进行定位,那么通过在不同的位置设置声源,就可以造就出更好的听觉感受。常见的声道有:

  1. 单声道, mono
  2. 双声道, stereo, 最常见的类型,包含左声道以及右声道
  3. 2.1声道,在双声道基础上加入一个低音声道
  4. 5.1声道,包含一个正面声道、左前方声道、右前方声道、左环绕声道、右环绕声道、一个低音声道,最早应用于早期的电影院
  5. 7.1声道,在5.1声道的基础上,把左右的环绕声道拆分为左右环绕声道以及左右后置声道,主要应用于BD以及现代的电影院

码率

码率也就是每秒的传输速率(也叫比特率),压缩的音频文件常用倍速来表示,比如达到CD音质的MP3是128kbps/44100HZ。注意这里的单位是bit而不是Byte,一个Byte等于8个bit(位),bit是最小的单位,一般用于网络速度的描述和各种通信速度,Byte则用于计算硬盘,内存的大小。

使用FFmpeg音频滤镜进行转码

不同领域对音频的播放要求是不一样的,所以需要针对不同的领域对音频参数进行调整,这里介绍一下如何通过音频滤镜调整音频数据的相关参数,对应的实现如下:

#include "../audio_filter.h"

extern "C" 
{
    
    
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavfilter/avfilter.h>
#include <libswresample/swresample.h>
}

#include <string>


/**@brief 转换音频数据的格式
* @param[in]  output_filename 输出文件名称
* @param[in]  input_filename 输入文件名称
* @param[in]  sample_fmt 采样格式
* @param[in]  sample_rate 采样率
* @param[in]  channel_layout 通道布局
* @param[in]  bitrate 码率
* @return  函数执行结果
* - 0     成功
* - 其它  失败
*/
int transcode_audio(const char *output_filename, const char *input_filename, AVSampleFormat sample_fmt,
	int sample_rate, uint64_t channel_layout, uint64_t bitrate) 
{
    
    
	//输入输出格式
	AVFormatContext *inFmtCtx = nullptr;
	AVFormatContext *outFmtCtx = nullptr;

	//解码器和编码器
	AVCodecContext *aDecCtx = nullptr;
	AVCodecContext *aEncCtx = nullptr;

	//输出流
	AVStream *aOutStream = nullptr;

	int ret;
	// open input file
	ret = avformat_open_input(&inFmtCtx, input_filename, nullptr, nullptr);
	ret = avformat_find_stream_info(inFmtCtx, nullptr);
	
	// open output file
	avformat_alloc_output_context2(&outFmtCtx, nullptr, nullptr, output_filename);
	for (int i = 0; i < inFmtCtx->nb_streams; ++i)
	{
    
    
		AVStream *inStream = inFmtCtx->streams[i];
		if (inStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
		{
    
    
			//输入流的解码器
			AVCodec *decoder = avcodec_find_decoder(inStream->codecpar->codec_id);
			aDecCtx = avcodec_alloc_context3(decoder);
			ret = avcodec_parameters_to_context(aDecCtx, inStream->codecpar);
			ret = avcodec_open2(aDecCtx, decoder, nullptr);

			//输出流的编码器
			AVCodec *encoder = avcodec_find_encoder(outFmtCtx->oformat->audio_codec);
			aOutStream = avformat_new_stream(outFmtCtx, encoder);
			aOutStream->id = outFmtCtx->nb_streams - 1;
			aEncCtx = avcodec_alloc_context3(encoder);

			aEncCtx->codec_id = encoder->id;
			aEncCtx->sample_fmt = sample_fmt ? sample_fmt : aDecCtx->sample_fmt;
			aEncCtx->sample_rate = sample_rate ? sample_rate : aDecCtx->sample_rate;
			aEncCtx->channel_layout = channel_layout;
			aEncCtx->channels = av_get_channel_layout_nb_channels(channel_layout);
			aEncCtx->bit_rate = bitrate ? bitrate : aDecCtx->bit_rate;
			aEncCtx->time_base = {
    
     1, aEncCtx->sample_rate };
			aOutStream->time_base = aEncCtx->time_base;
			if (outFmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
				aEncCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
			avcodec_open2(aEncCtx, encoder, nullptr);

			ret = avcodec_parameters_from_context(aOutStream->codecpar, aEncCtx);
			av_dict_copy(&aOutStream->metadata, inStream->metadata, 0);
			break;
		}
	}


	if (!(outFmtCtx->oformat->flags & AVFMT_NOFILE)) 
	{
    
    
		ret = avio_open(&outFmtCtx->pb, output_filename, AVIO_FLAG_WRITE);
		if (ret < 0) {
    
    
			return -1;
		}
	}

	ret = avformat_write_header(outFmtCtx, nullptr);
	if (ret < 0) 
	{
    
    
		return -1;
	}

	AVFrame *inAudioFrame = av_frame_alloc();
	AVFrame *outAudioFrame = av_frame_alloc();

	outAudioFrame->format = aEncCtx->sample_fmt;
	outAudioFrame->sample_rate = aEncCtx->sample_rate;
	outAudioFrame->channel_layout = aEncCtx->channel_layout;
	outAudioFrame->nb_samples = aEncCtx->frame_size;
	ret = av_frame_get_buffer(outAudioFrame, 0);
	
	int64_t audio_pts = 0;

	//修改音频数据包格式的滤镜
	AudioFilter filter;
	char description[512];
	AudioConfig inConfig(aDecCtx->sample_fmt, aDecCtx->sample_rate, aDecCtx->channel_layout, aDecCtx->time_base);
	AudioConfig outConfig(aEncCtx->sample_fmt, aEncCtx->sample_rate, aEncCtx->channel_layout, aEncCtx->time_base);
	char ch_layout[64];
	av_get_channel_layout_string(ch_layout, sizeof(ch_layout),
		av_get_channel_layout_nb_channels(aEncCtx->channel_layout), aEncCtx->channel_layout);
	snprintf(description, sizeof(description),
		"[in]aresample=sample_rate=%d[res];[res]aformat=sample_fmts=%s:sample_rates=%d:channel_layouts=%s[out]",
		aEncCtx->sample_rate,
		av_get_sample_fmt_name(aEncCtx->sample_fmt),
		aEncCtx->sample_rate,
		ch_layout);
	filter.create(description, &inConfig, &outConfig);
	filter.dumpGraph();

	while (true) 
	{
    
    
		//解析音频帧并通过滤镜进行处理
		AVPacket inPacket{
    
     nullptr };
		av_init_packet(&inPacket);
		ret = av_read_frame(inFmtCtx, &inPacket);
		if (ret == AVERROR_EOF) 
		{
    
    
			break;
		}
		else if (ret < 0) 
		{
    
    
			return -1;
		}

		if (inPacket.stream_index == aOutStream->index) 
		{
    
    
			ret = avcodec_send_packet(aDecCtx, &inPacket);
			if (ret != 0) 
			{
    
    
				printf("send packet error\n");
			}
			ret = avcodec_receive_frame(aDecCtx, inAudioFrame);

			if (ret == 0) 
			{
    
    
				
				ret = filter.addInput1(inAudioFrame);
				av_frame_unref(inAudioFrame);
				if (ret < 0)
				{
    
    
					printf("add filter input1 error\n");
				}

				do {
    
    
					outAudioFrame->nb_samples = aEncCtx->frame_size;
					ret = filter.getFrame(outAudioFrame);
					if (ret == 0) 
					{
    
    
						outAudioFrame->pts = audio_pts;
						audio_pts += outAudioFrame->nb_samples;

						ret = avcodec_send_frame(aEncCtx, outAudioFrame);
						if (ret < 0) {
    
    
							printf("unable to send frame: %s\n");
						}
					}
					else 
					{
    
    
						printf("unable to get filter audio frame: %s\n");
						break;
					}

					do {
    
    
						AVPacket outPacket{
    
     nullptr };
						av_init_packet(&outPacket);
						ret = avcodec_receive_packet(aEncCtx, &outPacket);
						if (ret == 0) 
						{
    
    
							av_packet_rescale_ts(&outPacket, aEncCtx->time_base, aOutStream->time_base);
							outPacket.stream_index = aOutStream->index;
							
							ret = av_interleaved_write_frame(outFmtCtx, &outPacket);
							if (ret < 0) {
    
    
								printf("unable to write packet\n");
								break;
							}
						}
						else {
    
    
							printf("unable to receive packet\n");
							break;
						}
					} while (true);

				} while (true);

			}
			else 
			{
    
    
				printf("unable to receive frame\n");
			}
		}
	}

	//清理数据缓存
	int eof = 0;
	do {
    
    
		ret = filter.getFrame(outAudioFrame);
		if (ret == 0) 
		{
    
    
			outAudioFrame->pts = audio_pts;
			audio_pts += outAudioFrame->nb_samples;
		}
		else 
		{
    
    
			printf("filter queue finished\n");
		}

		ret = avcodec_send_frame(aEncCtx, ret == 0 ? outAudioFrame : nullptr);
		do {
    
    
			AVPacket outPacket{
    
     nullptr };
			ret = avcodec_receive_packet(aEncCtx, &outPacket);
			if (ret == 0) {
    
    
				av_packet_rescale_ts(&outPacket, aEncCtx->time_base, aOutStream->time_base);
				outPacket.stream_index = aOutStream->index;
				
				ret = av_interleaved_write_frame(outFmtCtx, &outPacket);
				if (ret < 0) 
				{
    
    
					eof = 1;
					break;
				}
			}
			else if (ret == AVERROR_EOF) 
			{
    
    
				eof = 1;
				break;
			}
			else {
    
    
				break;
			}
		} while (true);

	} while (!eof);

	//释放对应的资源
	filter.destroy();
	av_write_trailer(outFmtCtx);
	avformat_close_input(&inFmtCtx);
	av_frame_free(&inAudioFrame);
	av_frame_free(&outAudioFrame);
	avcodec_free_context(&aDecCtx);
	avcodec_free_context(&aEncCtx);
	avformat_free_context(inFmtCtx);
	avformat_free_context(outFmtCtx);

	return 0;
}

这里我们将音频文件的采样格式修改为AV_SAMPLE_FMT_FLTP,同时我们将采样率降低为22050,码率修改为80kbps。

int main(int argc, char* argv[])
{
    
    
	if (argc != 3)
	{
    
    
		printf("usage:%1 input filepath %2 outputfilepath");
		return -1;
	}

	//输入文件地址、输出文件地址
	std::string fileInput = std::string(argv[1]);
	std::string  fileOutput = std::string(argv[2]);
	transcode_audio(fileOutput.c_str(), fileInput.c_str(),(AVSampleFormat)AV_SAMPLE_FMT_FLTP,22050, AV_CH_LAYOUT_STEREO,80000);
}

参考链接

参考链接:https://www.cnblogs.com/yongdaimi/p/10722355.html

猜你喜欢

转载自blog.csdn.net/yang1fei2/article/details/128992503