FFMPEG实现PCM编码(采用封装格式实现)

技术在于交流、沟通,转载请注明出处并保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/81260882

 

[音频编解码系列文章]

  1. 音频编解码基础
  2. FFMPEG实现音频重采样
  3. FFMPEG实现PCM编码(采用封装格式实现)
  4. FFMPEG实现PCM编码(不采用封装格式实现)
  5. FAAC库实现PCM编码
  6. FAAD库实现RAW格式AAC解码
  7. FAAD库实现RAW格式AAC封装成ADTS格式
  8. FAAD库实现ADTS格式解码
  9. FFMPEG实现对AAC解码(采用封装格式实现)
  10. FFMPEG实现对AAC解码(不采用封装格式实现)

本篇基于FFMPEG实现把PCM编码成AAC或者MP3格式的视频文件,编码的比特率都是64kbps,代码中AAC格式编码不需要音频重采样,而MP3格式编码只支持样本平行存储的方式。在编码中,首先PCM文件的格式是一种编码方式,这种是波形编码,而各种压缩算法实现本身会支持一个格式,如对通道,样本格式的要求,所以当PCM文件格式与编码实现不一致时,就需要对PCM数据重采样,然后编码。MP3格式据说编码比特率在128Kbps的情况下,高频部分损失比较严重,后面可以分析一下。本篇编码是把AAC或者MP3当做一种封装格式如MP4这种封装格式进行的,下篇将介绍一种只打开编码器,不打开封装格式,获取原始数据直接送入编码器的方式。ffmpeg编码的AAC是ADTS格式。

PCM编码AAC或者MP3代码:

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------

Date Created:	2014-10-25
Author:			wubihe QQ:1269122125 Email:1269122125@qq.com
Description:	代码实现PCM编码AAC,MP3格式
--------------------------------------------------------------------------------
Modification History
DATE          AUTHOR          DESCRIPTION
--------------------------------------------------------------------------------

********************************************************************************/
#include <stdio.h>

#define __STDC_CONSTANT_MACROS

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

#define INPUT_FILE_NAME			("huangdun_r48000_FMT_S16_c2.pcm")
//输出文件前缀
#define OUTPUT_FILE_NAME_PREFIX	("huangdun")
//输出文件后缀
//#define OUTPUT_FILE_NAME_SUFFIX	("aac")
//输出文件后缀
#define OUTPUT_FILE_NAME_SUFFIX	("mp3")
//输出文件比特率 该值越大 音频质量越好 音质损失越小
#define OUTPUT_FILE_BIT_RATE	(64000)

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
	int ret;
	int got_frame;
	AVPacket enc_pkt;
	if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &CODEC_CAP_DELAY))
		return 0;
	while (1) 
	{
		enc_pkt.data = NULL;
		enc_pkt.size = 0;
		av_init_packet(&enc_pkt);
		//输入视频帧为NULL
		ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame);
		av_frame_free(NULL);
		if (ret < 0)
			break;
		if (!got_frame)
		{
			ret=0;
			break;
		}
		printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
		/* mux encoded frame */
		ret = av_write_frame(fmt_ctx, &enc_pkt);
		if (ret < 0)
			break;
	}
	return ret;
}

int main()
{

	static char*pFormatName[]=
	{
		"FMT_U8","FMT_S16","FMT_S32","FMT_FLT","FMT_DBL",
		"FMT_U8P","FMT_S16P","FMT_S32P","FMT_FLTP","FMT_DBLP"
	};

	//各种不同格式对应字节数
	static int mapSampleBytes[AV_SAMPLE_FMT_NB]
	={1,2,4,4,8,1,2,4,4,8};

	//PCM原始数据格式
	uint64_t iInputLayout				= AV_CH_LAYOUT_STEREO;
	int      iInputChans				= av_get_channel_layout_nb_channels(iInputLayout);
	AVSampleFormat eInputSampleFormat   = AV_SAMPLE_FMT_S16;
	int	     iInputSampleRate			= 48000;
	//不同样本格式长度
	int iInputSampleBytes				= mapSampleBytes[eInputSampleFormat];
	

	//PCM需要重采样的格式 部分编码器不支持原始PCM的数据格式如MP3
	uint64_t iOutputLayout				= AV_CH_LAYOUT_STEREO;
	int      iOutputChans				= av_get_channel_layout_nb_channels(iOutputLayout);
	AVSampleFormat eOutputSampleFormat ;
	int	     iOutputSampleRate			= 48000;


	if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"aac") == 0)
	{
		eOutputSampleFormat	  = AV_SAMPLE_FMT_S16;
	}
	else if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"mp3") == 0)
	{
		//MP3不支持AV_SAMPLE_FMT_S16这种格式
		eOutputSampleFormat	  = AV_SAMPLE_FMT_S16P;
	}
	else
	{

	}
	//编码样本长度
	int iOutputSampleBytes	= mapSampleBytes[eOutputSampleFormat];

	//是否需要重采样
	bool bNeedResample = false;
	if(eInputSampleFormat != eOutputSampleFormat)
	{
		bNeedResample = true;
	}

	//是否平面存储结构
	bool bPlanner      = false;
	if((eOutputSampleFormat>=AV_SAMPLE_FMT_U8P) &&(eOutputSampleFormat<=AV_SAMPLE_FMT_DBLP))
	{
		bPlanner      = true;
	}
	//打开输入文件
	FILE *pInputFile = fopen("huangdun_r48000_FMT_S16_c2.pcm", "rb");
	if(pInputFile == NULL)
	{

	}
	
	//打开输出文件
	char szOutFileName[256]={0};
	sprintf(szOutFileName,"%s_br%d_sr%d.%s",OUTPUT_FILE_NAME_PREFIX,OUTPUT_FILE_BIT_RATE,iOutputSampleRate,OUTPUT_FILE_NAME_SUFFIX);
	FILE *pOutputFile = fopen(szOutFileName, "wb");

	//打开中间测试文件
	char szTempFileName[256]={0};
	sprintf(szTempFileName,"%s_sr%d_c1.pcm",OUTPUT_FILE_NAME_PREFIX,iOutputSampleRate);
	FILE *pTempFile = fopen(szTempFileName, "wb");

	int iReturn;

	///编码器操作
	//注册所有编解码器
	av_register_all();	

	//封装格式上下文 AVFormatContext中有AVInputFormat和AVOutputFormat
	//解复用时avformat_open_input()初始化AVInputFormat,复用时用户自己初始化AVOutputFormat
	AVFormatContext* pFormatCtx;
	AVOutputFormat * fmt;

	//Method 1.分配一个封装格式
	pFormatCtx = avformat_alloc_context();
	//根据后缀名 填充 输出格式上下文
	fmt = av_guess_format(NULL, szOutFileName, NULL);
	pFormatCtx->oformat = fmt;


	//Method 2.
	//avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);
	//fmt = pFormatCtx->oformat;

	//添加一个流
	AVStream  *audio_st = avformat_new_stream(pFormatCtx, 0);
	if (audio_st==NULL)
	{
		return -1;
	}

	//添加一个输出路径
	if (avio_open(&pFormatCtx->pb,szOutFileName, AVIO_FLAG_READ_WRITE) < 0)
	{
		printf("Failed to open output file!\n");
		return -1;
	}

	//Show some information 日志信息
	av_dump_format(pFormatCtx, 0, szOutFileName, 1);

	//初始化编码器相关结构体 获取输出流中的编码上下文
	AVCodecContext* pCodecCtx = audio_st->codec;

	pCodecCtx->codec_id		  = fmt->audio_codec	;
	pCodecCtx->codec_type	  = AVMEDIA_TYPE_AUDIO	;
	//立体声
	pCodecCtx->channel_layout = iOutputLayout	;
	pCodecCtx->channels		  = iOutputChans    ;
	//编码比特率 AAC支持多种比特率 一般比特率越高 视频质量越好 需要传输带宽越大
	pCodecCtx->bit_rate		  = OUTPUT_FILE_BIT_RATE; 
	pCodecCtx->sample_rate    = iOutputSampleRate;
	//PCM样本深度为AV_SAMPLE_FMT_S16 但不是所有格式的编码都支持这种格式
	pCodecCtx->sample_fmt	  = eOutputSampleFormat;


	//编码器
	AVCodec* pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
	if (!pCodec)
	{
		printf("Can not find encoder!\n");
		return -1;
	}

	//打开解码器 有可能失败 -22 错误,原因不同的编码格式支持的样本格式不一样
	//如封装AAC格式样本格式是AV_SAMPLE_FMT_FLT,打开就出错
	if ((iReturn = avcodec_open2(pCodecCtx, pCodec,NULL)) < 0)
	{
		printf("Failed to open encoder :[%d]!\n",iReturn);
		return -1;
	}




	//重采用上下文
	SwrContext *pSwrCtx = NULL;


	//原始数据帧
	AVFrame* pRawframe  = NULL;
	//原始帧一Planer的大小 非平面分布的情况就是缓存总大小
	int iRawLineSize = 0;
	//原始帧缓存大小
	int iRawBuffSize = 0;
	//原始帧缓存
	uint8_t *pRawBuff= NULL;



	//重采样后数据帧
	AVFrame* pConvertframe = NULL;
	//重采样后一Planer的大小
	int iConvertLineSize  = 0;
	//重采样后缓存大小
	int iConvertBuffSize  = 0;
	//重采样后帧缓存
	uint8_t *pConvertBuff = NULL;

	//1帧数据样本数
	int iFrameSamples = pCodecCtx->frame_size;



	// 存储原始数据 
	iRawLineSize = 0;
	iRawBuffSize  = av_samples_get_buffer_size(&iRawLineSize, iInputChans, iFrameSamples, eInputSampleFormat, 0);
	pRawBuff = (uint8_t *)av_malloc(iRawBuffSize);

	//原始数据保存在AVFrame结构体中
	pRawframe = av_frame_alloc();

	pRawframe->nb_samples	= iFrameSamples;
	pRawframe->format		= eInputSampleFormat;
	pRawframe->channels     = iInputChans;


	iReturn = avcodec_fill_audio_frame(pRawframe, iInputChans, eInputSampleFormat, (const uint8_t*)pRawBuff, iRawBuffSize, 0);
	if(iReturn<0)
	{
		return -1;
	}

	
	if(bNeedResample)
	{

		pSwrCtx = swr_alloc_set_opts(NULL,iOutputLayout, eOutputSampleFormat, iOutputSampleRate,
			iInputLayout,eInputSampleFormat , iInputSampleRate,0, NULL);
		swr_init(pSwrCtx);

		// 存储转换后数据 
		iConvertLineSize  = 0;
		iConvertBuffSize  = av_samples_get_buffer_size(&iConvertLineSize, iOutputChans, iFrameSamples, eOutputSampleFormat, 0);
		pConvertBuff      = (uint8_t *)av_malloc(iConvertBuffSize);

		//转换后数据保存在AVFrame结构体中
		pConvertframe = av_frame_alloc();
		pConvertframe->nb_samples	= iFrameSamples;
		pConvertframe->format		= eOutputSampleFormat;
		pConvertframe->channels     = iOutputChans;

		iReturn = avcodec_fill_audio_frame(pConvertframe, iOutputChans, eOutputSampleFormat, (const uint8_t*)pConvertBuff, iConvertBuffSize, 0);
		if(iReturn<0)
		{
			return -1;
		}

	}

	

	//编码以后的数据是AVPacket
	AVPacket pkt;
	if(!bNeedResample)
	{
		av_new_packet(&pkt,iRawBuffSize);
	}
	else
	{
		av_new_packet(&pkt,iConvertBuffSize);
	}
	

	//Write Header
	avformat_write_header(pFormatCtx,NULL);




	//统计读取样本数
	long long lReadTotalSamples = 0;
	//每次读取样本数
	int iReadSamples;
	//统计所有的帧数
	int iFrameNum =0;
	//是否编码成功
	int got_frame =0;
	//临时
	AVFrame* pTempFrame=NULL;
	//读取数据 保存在pConvertframe->data
	int iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);
	while(iRealRead>0)
	{
		iReadSamples = iRealRead/(iInputSampleBytes*iInputChans);
		if(bNeedResample)
		{
			swr_convert(pSwrCtx, (uint8_t**)pConvertframe->data, iFrameSamples ,(const uint8_t**)pRawframe->data, iFrameSamples );
			
			if(bPlanner)
			{ 
				//只保存一个通道 因为保存多个通道测试工具 audacity看不了
				fwrite(pConvertframe->data[0],pConvertframe->linesize[0],1,pTempFile);
			}
			printf("Convert Frame :%d\n",++iFrameNum);
			pTempFrame = pConvertframe;
		}
		else
		{
			pTempFrame = pRawframe;
		}
		


		pTempFrame->pts		    = lReadTotalSamples;
		got_frame				= 0;
		//Encode
		if(avcodec_encode_audio2(pCodecCtx, &pkt,pTempFrame, &got_frame)<0)
		{
			printf("Failed to encode!\n");
			return -1;
		}
		if (got_frame==1)
		{
			printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);
			pkt.stream_index = audio_st->index;
			av_write_frame(pFormatCtx, &pkt);
			av_free_packet(&pkt);
		}
		
		//统计样本数以转换前为准 转换前后样本数是一样的
		lReadTotalSamples += (iReadSamples);

		iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);
	}

	//刷新编码器
	if(flush_encoder(pFormatCtx,0)<0)
	{
		printf("Flushing encoder failed\n");
		return -1;
	}

	fclose(pInputFile);
	fclose(pOutputFile);
	fclose(pTempFile);

	av_free(pRawBuff);

	if(bNeedResample)
	{
		av_free(pConvertBuff);
		swr_free(&pSwrCtx);
	}
	printf("Convert Success!!\n");
	getchar();
	return 0;

}

 

 

编码AAC生成文件 huangdun_br64000_sr48000.aac,可以直接用普通播放器播放如VLC

编码mp3生成文件 huangdun_br64000_sr48000.mp3,可以直接用普通播放器播放如VLC

MP3格式频谱分析

原始PCM格式频谱图

编码MP3后的频谱图

从上面频谱图可以看出MP3格式编码对高频部分衰减确实很厉害,图中11000HZ---15000HZ部分全部衰减掉了。现在把程序中编码比特率提高到128Kbps,来看看效果。

可以看到高频部分15000HZ衰减有明显减少,由此得出结论MP3编码格式优点压缩率高,能够在低码率的情况下提高较好的音质,适合网络传输,但是缺点是比特率低于128kbps的情况,高频部分有很高的衰减。

DEMO编译环境:   Win7_64bit+VS2008

DEMO下载地址:https://download.csdn.net/download/hiwubihe/10569791

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 下面是一个简单的示例代码,展示了如何使用 Java 将 PCM 数据流转换为 TRMP 数据流: ``` import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import org.jaudiotagger.audio.AudioFileIO; import org.jaudiotagger.audio.mp3.MP3AudioHeader; import org.jaudiotagger.audio.mp3.MP3File; public class PCMToTRMP { public static void main(String[] args) throws Exception { // 定义 PCM 数据流的格式 AudioFormat pcmFormat = new AudioFormat(44100, 16, 2, true, false); // 读取 PCM 数据流 AudioInputStream pcmStream = AudioSystem.getAudioInputStream(pcmFile); // 将 PCM 数据流转换为 TRMP 数据流 ByteArrayOutputStream trmpStream = new ByteArrayOutputStream(); AudioSystem.write(pcmStream, AudioFileFormat.Type.MP3, trmpStream); pcmStream.close(); // 将 TRMP 数据流写入文件 File trmpFile = new File("output.mp3"); FileOutputStream fos = new FileOutputStream(trmpFile); fos.write(trmpStream.toByteArray()); fos.close(); } } ``` 在这段代码中,我们首先读取了一个 PCM 数据流,然后使用 `AudioSystem.write()` 方法将其转换为 TRMP 数据流,最后将转换后的 TRMP 数据流写入文件。 请注意,这段代码使用的是 javax.sound 包中的类,因此在使用这段代码之前,你需要在项目中添加对 javax.sound 包的引用。 希望这段代码能够帮助你。如果你有任何疑问,请随时追问。 ### 回答2: Java可以通过使用FFmpeg库来实现PCM流转为MP3流的示例代码。下面是一个简单的示例代码: ```java import org.bytedeco.ffmpeg.global.avcodec; import org.bytedeco.javacv.*; public class PCM2MP3Converter { public static void main(String[] args) { // 输入PCM流文件路径 String pcmFilePath = "path_to_pcm_file"; // 输出MP3流文件路径 String mp3FilePath = "path_to_mp3_file"; // 初始化FFmpegFrameGrabber FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(pcmFilePath); grabber.setAudioChannels(2); // 设置声道数 grabber.setSampleRate(44100); // 设置采样率 grabber.setAudioCodec(avcodec.AV_CODEC_ID_PCM_S16LE); // 设置音频编码器 grabber.start(); // 初始化FFmpegFrameRecorder FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(mp3FilePath, 2); recorder.setAudioChannels(2); // 设置声道数 recorder.setSampleRate(44100); // 设置采样率 recorder.setAudioCodec(avcodec.AV_CODEC_ID_MP3); // 设置音频编码器 recorder.setFormat("mp3"); // 设置输出格式 recorder.start(); Frame frame; // 逐帧读取PCM流并转码为MP3流 while ((frame = grabber.grabFrame()) != null) { recorder.record(frame); } recorder.stop(); grabber.stop(); } } ``` 在上述示例代码中,我们使用了JavaCV库,该库提供了对FFmpeg的Java封装,在Java中实现PCM转MP3的功能。首先,我们使用`FFmpegFrameGrabber`打开PCM流文件,设置了声道数、采样率和音频编码器。然后,我们再使用`FFmpegFrameRecorder`创建一个MP3流文件,并设置了相同的声道数、采样率和音频编码器。接着,我们逐帧读取PCM流,并通过`recorder`将其转码为MP3流。最后,我们分别停止`recorder`和`grabber`的运行。 需要注意的是,这里只是给出了一个简单的示例代码,实际使用时,你可能还需要根据自己的需求进行更多的配置和错误处理。 ### 回答3: 要实现PCM流转为MP3流,我们可以借助第三方库来实现。以下是一个示例代码: ```java import java.io.*; import javax.sound.sampled.*; public class PCMtoMP3Converter { public static void main(String[] args) { File inputFile = new File("input.pcm"); File outputFile = new File("output.mp3"); convertPCMtoMP3(inputFile, outputFile); } public static void convertPCMtoMP3(File inputFile, File outputFile) { try { AudioInputStream pcmStream = AudioSystem.getAudioInputStream(inputFile); AudioFormat pcmFormat = pcmStream.getFormat(); AudioFormat mp3Format = new AudioFormat(AudioFormat.Encoding.MPEG_1_LAYER_3, pcmFormat.getSampleRate(), 16, pcmFormat.getChannels(), pcmFormat.getChannels() * 2, pcmFormat.getSampleRate(), false); AudioInputStream mp3Stream = AudioSystem.getAudioInputStream(mp3Format, pcmStream); AudioSystem.write(mp3Stream, AudioFileFormat.Type.MP3, outputFile); pcmStream.close(); mp3Stream.close(); System.out.println("转换完成!"); } catch (UnsupportedAudioFileException | IOException e) { e.printStackTrace(); } } } ``` 在上面的代码中,我们首先指定输入的PCM文件路径和输出的MP3文件路径。然后,通过调用`convertPCMtoMP3()`方法,我们将PCM流转换为MP3流并输出到指定的文件。这里使用了`AudioSystem`类提供的方法来获取PCM流和MP3流,并通过`AudioSystem.write()`方法将MP3流写入文件中。 请注意,上述代码需要依赖Java的音频处理库,如Java Sound API。你可能需要在项目中添加相应的库文件。另外,由于PCM和MP3之间的转换涉及到音频编码和解码的复杂过程,所以在实际应用中可能需要更多的处理和配置。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值