[FFMPEG-4]重构从mp3中获取pcm数据

一直以来都想把自己的代码打磨成工业级别的,但是奈何实力有限,憋了两天,还是这个一个烂烂的代码。还是又很多硬编码的地方,而且没经过充分测试。

马上就放假回家了,想着一定要有一个结尾,不能把事情拖到寒假或者明年,就先把这个烂代码放上。算是一个总结。

主要实现了从mp3中提取一段时长的pcm数据和wav头。这个需求是在做基于内容的音乐检索的时候遇到的。上代码。

#include    <stdlib.h>
#include    <stdio.h>
#include    <string.h>
#include    <math.h>
#include 	<stdint.h>
#include	<malloc.h>

#include  	<libavutil/avutil.h>
#include  	<libavutil/attributes.h>
#include    <libavutil/channel_layout.h>
#include    <libavutil/common.h>
#include    <libavutil/file.h>
#include    <libavutil/imgutils.h>
#include    <libavutil/mathematics.h>
#include    <libavutil/opt.h>
#include    <libavutil/samplefmt.h>
#include    <libavutil/timestamp.h>

#include    <libavformat/avformat.h>
#include    <libavformat/avio.h>

#include    <libavcodec/avcodec.h>

#include    <libswscale/swscale.h>

#include    <libswresample/swresample.h>

#include 	"AudioFormatUtils.h"

//#define LOG_TAG "GETSTREAM"
//#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) // 定义LOGD类型
//#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) // 定义LOGI类型
//#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__) // 定义LOGW类型
//#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) // 定义LOGE类型
//#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__) // 定义LOGF类型

#define LOG_TAG stderr
#define LOGD(...) fprintf(LOG_TAG, __VA_ARGS__)
#define LOGI(...) fprintf(LOG_TAG, __VA_ARGS__)
#define LOGW(...) fprintf(LOG_TAG, __VA_ARGS__)
#define LOGE(...) fprintf(LOG_TAG, __VA_ARGS__)
#define LOGF(...) fprintf(LOG_TAG, __VA_ARGS__)

uint8_t *resdatas = NULL;
unsigned char header[44];

int get_wav_header(unsigned char header[], WavHeader *wavHeader) {
	//unsigned char header[44];
	unsigned char* p = header;
	unsigned int bytes = (wavHeader->bits_per_sample + 7) / 8;
	float data_size = (float) bytes * wavHeader->total_samples;
	unsigned long word32;

	*p++ = 'R';
	*p++ = 'I';
	*p++ = 'F';
	*p++ = 'F'; //RIFF 4

	word32 = (unsigned long) data_size + (44 - 8);
	*p++ = (unsigned char) (word32 >> 0);
	*p++ = (unsigned char) (word32 >> 8);
	*p++ = (unsigned char) (word32 >> 16);
	*p++ = (unsigned char) (word32 >> 24); //数据长度 4+4

	*p++ = 'W';
	*p++ = 'A';
	*p++ = 'V';
	*p++ = 'E'; //WAVE 4+4+4

	*p++ = 'f';
	*p++ = 'm';
	*p++ = 't';
	*p++ = ' '; //fmt 4+4+4+4

	*p++ = 0x10;
	*p++ = 0x00;
	*p++ = 0x00;
	*p++ = 0x00; //16  4+4+4+4+4=20

	*p++ = 0x01;
	*p++ = 0x00; //1   4+4+4+4+4+2=22

	*p++ = (unsigned char) (wavHeader->channels >> 0);
	*p++ = (unsigned char) (wavHeader->channels >> 8); //声道数   4+4+4+4+4+2+2=24

	word32 = (unsigned long) (wavHeader->samplerate + 0.5);
	*p++ = (unsigned char) (word32 >> 0);
	*p++ = (unsigned char) (word32 >> 8);
	*p++ = (unsigned char) (word32 >> 16);
	*p++ = (unsigned char) (word32 >> 24); //采样率   4+4+4+4+4+2+2+4=28

	word32 = wavHeader->samplerate * bytes * wavHeader->channels;
	*p++ = (unsigned char) (word32 >> 0);
	*p++ = (unsigned char) (word32 >> 8);
	*p++ = (unsigned char) (word32 >> 16);
	*p++ = (unsigned char) (word32 >> 24); //每秒所需字节数  4+4+4+4+4+2+2+4=32

	word32 = bytes * wavHeader->channels;
	*p++ = (unsigned char) (word32 >> 0);
	*p++ = (unsigned char) (word32 >> 8); // blockAlign(2个字节) 4+4+4+4+4+2+2+4+2=34
	//每个采样需要的字节数,计算公式:声道数 * 每个采样需要的bit  / 8

	*p++ = (unsigned char) (wavHeader->bits_per_sample >> 0);
	*p++ = (unsigned char) (wavHeader->bits_per_sample >> 8); //bitPerSample(2个字节)4+4+4+4+4+2+2+4+2+2=36
	//每个采样需要的bit数,一般为8或16

	*p++ = 'd';
	*p++ = 'a';
	*p++ = 't';
	*p++ = 'a'; //data 4+4+4+4+4+2+2+4+2+2+4=40

	word32 = (unsigned long) data_size;
	*p++ = (unsigned char) (word32 >> 0);
	*p++ = (unsigned char) (word32 >> 8);
	*p++ = (unsigned char) (word32 >> 16);
	*p++ = (unsigned char) (word32 >> 24);
	//size2(4个字节)   4+4+4+4+4+2+2+4+2+2+4+4=44
	// 录音数据的长度,不包括头部长度

	return 0;
}
/**
 * 获取一定时长的pcm数据.
 * @param inFile 输入文件.
 * @param start_pos_s 起始位置,单位为秒.
 * @param fmt len_s 持续长度,单位为秒.
 * @param channel_num  声道数.
 * @param bit_rate  比特率.
 * @param sample_rate  采样率.
 * @return 成功时获取的数据长度,失败时返回-1.
 */
int get_wav_stream(const char * inFile, double start_pos_s, double len_s,
		int channel_num, int bit_rate, int sample_rate) {

	int res_data_size = 0;

	FILE *fout;
	AVPacket packet;
	AVFrame* frame = NULL;
	AVFormatContext* formatContext = NULL;
	AVStream* audioStream = NULL;
	AVCodecContext* codecContext = NULL;
	SwrContext* swrContext = NULL;
	frame = av_frame_alloc();
	if (!frame) {
		LOGE("Error allocating the frame");
		goto end;
	}

	if (avformat_open_input(&formatContext, inFile, NULL, NULL) != 0) {
		LOGE("Error opening the file");
		goto end;
	}

	if (avformat_find_stream_info(formatContext, NULL) < 0) {
		LOGE("Error finding the stream info");
		goto end;
	}


	double end_pos_s = (start_pos_s + len_s); //请求的时长,单位为内部时长
	double duration = formatContext->duration / AV_TIME_BASE; // AV_TIME_BASE; //文件总时长

	if (start_pos_s > duration) {
		LOGE("Start time is biger than the total time.");
		goto end;
	}

	if ((end_pos_s > duration) || len_s == 0) {
		end_pos_s = duration;
		len_s = duration - start_pos_s;
	}
	LOGE("len_s= %f\n", len_s);



	unsigned int i;
	for (i = 0; i < formatContext->nb_streams; ++i) {
		if (formatContext->streams[i]->codec->codec_type
				== AVMEDIA_TYPE_AUDIO) {
			audioStream = formatContext->streams[i];
			break;
		}
	}
	if (audioStream == NULL) {
		LOGE("Could not find any audio stream in the file");
		goto end;
	}

	codecContext = audioStream->codec;
	codecContext->codec = avcodec_find_decoder(codecContext->codec_id);
	if (codecContext->codec == NULL) {
		LOGE("Couldn't find a proper decoder");
		goto end;
	} else if (avcodec_open2(codecContext, codecContext->codec, NULL) != 0) {
		LOGE("Couldn't open the context with the decoder");
		goto end;
	}

	/*START*************************START***********************START*/
	/*设置输出格式,为了未来jni调用的简单,所以都传的int,反正是自己用的,直观一些*/
	int64_t outChannelLayout;
	if (channel_num == 1) {
		outChannelLayout = AV_CH_LAYOUT_MONO;
	} else {
		outChannelLayout = AV_CH_LAYOUT_STEREO;
	}

	enum AVSampleFormat outSampleFormat;
	if (bit_rate == 32) {
		outSampleFormat = AV_SAMPLE_FMT_S32;
	} else if (bit_rate == 8) {
		outSampleFormat = AV_SAMPLE_FMT_U8;
	} else {
		outSampleFormat = AV_SAMPLE_FMT_S16;
	}

	int outSampleRate = sample_rate;

	swrContext = swr_alloc_set_opts(NULL, outChannelLayout, outSampleFormat,
			outSampleRate,
			av_get_default_channel_layout(codecContext->channels),
			codecContext->sample_fmt, codecContext->sample_rate, 0, NULL);
	if (swrContext == NULL) {
		LOGE( "Couldn't create the SwrContext");
		goto end;
	}
	if (swr_init(swrContext) != 0) {
		LOGE("Couldn't initialize the SwrContext");
		goto end;
	}
	/*END**************************END*************************END*/

	/*START**************************Seek to pos*************************START*/
	int seek_pos = start_pos_s * AV_TIME_BASE;
	LOGE("AV_TIME_BASE=%d seek_pos=%d\n", AV_TIME_BASE, seek_pos);
	if (formatContext->start_time != AV_NOPTS_VALUE)
		seek_pos += formatContext->start_time;
	if (av_seek_frame(formatContext, -1, seek_pos, AVSEEK_FLAG_BACKWARD) < 0) {
		LOGE(
				"%s,av_seek_frame() seek to %.3f failed!", __FUNCTION__, (double)seek_pos/AV_TIME_BASE);
		goto end;
	}
	/*END**************************seek END*************************END*/

	int total_samples = len_s * sample_rate;
	LOGE("total_samples=%d\n", total_samples);
	int res_bufsize = av_samples_get_buffer_size(0, channel_num, total_samples,
			outSampleFormat, 1);
	if(!res_bufsize){
		LOGE("av_samples_get_buffer_size error");
		goto end;
	}
	LOGE("res_bufsize=%d\n", res_bufsize);

	//for transfout = fopen("fwrite.wav", "w");
	//fseek(fout, 44, SEEK_SET);


	WavHeader h;
	h.bits_per_sample = bit_rate;
	h.channels = channel_num;
	h.samplerate = sample_rate;
	h.total_samples = res_bufsize;
	get_wav_header(header, &h);

	av_init_packet(&packet);
	while (av_read_frame(formatContext, &packet) == 0) {
		if (packet.stream_index == audioStream->index) {
			AVPacket decodingPacket = packet;

			while (decodingPacket.size > 0) {
				// Try to decode the packet into a frame
				int frameFinished = 0;
				int result = avcodec_decode_audio4(codecContext, frame,
						&frameFinished, &decodingPacket);

				if (result < 0 || frameFinished == 0) {
					break;
				}

				int dst_data_size = (outSampleRate / frame->sample_rate)
						* frame->nb_samples * channel_num * (bit_rate + 7) / 8
						+ (bit_rate + 7) / 8;

				uint8_t buffer[dst_data_size];
				uint8_t* pointers[dst_data_size];
				pointers[0] = &buffer[0];

				int numSamplesOut = swr_convert(swrContext, pointers,
						dst_data_size / channel_num,
						(const unsigned char**) frame->extended_data,
						frame->nb_samples);
				int dst_bufsize = av_samples_get_buffer_size(0, channel_num,
						numSamplesOut, outSampleFormat, 1);

				///filefwrite(pointers[0], 1, dst_bufsize, fout);

				int tmp = res_data_size + dst_bufsize;

				int sign = 1;
				if (tmp > res_bufsize) {
					dst_bufsize -= (tmp - res_bufsize);
					sign = 0;
				}

				resdatas = realloc(resdatas, res_data_size + dst_bufsize);
				int i = 0;
				for (i = res_data_size; i < res_data_size + dst_bufsize; i++) {
					resdatas[i] = buffer[i - res_data_size];
				}

				if (!sign) {
					break;
				}

				res_data_size += dst_bufsize;
				decodingPacket.size -= result;
				decodingPacket.data += result;
			}

		}
		av_free_packet(&packet);
	}
	fprintf(stderr, "count=%d\n", res_data_size);

	//fseek(fout, 0, SEEK_SET);
	///filefwrite(&header, 1, 44, fout);

	end: if (frame) {
		av_free(frame);
	}
	if (codecContext) {
		avcodec_close(codecContext);
	}
	if (formatContext) {
		avformat_close_input(&formatContext);
	}
	if (swrContext) {
		swr_free(&swrContext);
	}

	return res_data_size > 0 ? res_data_size : -1;

}

int main(int argc, char** argv) {
	char *file = "ping.mp3";
	double start = 0;
	double len = 12;
	int channel = 2;
	int bit = 16;
	int sample = 44100;

	int res_size = get_wav_stream(file, start, len, channel, bit, sample);
	FILE *out_file = fopen("out.pcm", "w");
	fwrite(resdatas, 1, res_size, out_file);
	return 1;
}

对了,还有一个数据结构。

typedef struct {


unsigned long samplerate;
unsigned int bits_per_sample;
unsigned int channels;
unsigned long total_samples;


} WavHeader;

ok,就酱紫了先。



<think>嗯,用户让我解释这个ffmpeg命令:ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le output.pcm。首先,我需要确认自己对这个命令的理解是否正确。 首先,ffmpeg是一个强大的多媒体处理工具,可以转换音频和视频格式。用户这里是要把MP3文件转成PCM格式。命令中的参数需要逐个分析。-i input.mp3是输入文件,没问题。-f s16le应该是指定输出格式为s16le,也就是有符号16位小端PCM。然后-acodec pcm_s16le是设置音频编解码器为pcm_s16le,可能和-f参数有些重复,但可能为了确保正确指定。输出文件是output.pcm。 接下来,我需要考虑用户的需求是什么。他们可能在做音频处理,比如需要原始PCM数据进行进一步处理,或者与某些硬件设备交互,因为PCM是未经压缩的原始音频数据。用户可能不太清楚各个参数的具体作用,或者想确认命令是否正确。 可能用户遇到的疑惑点在于为什么需要同时使用-f和-acodec参数。这时候需要解释这两个参数的关系。-f指定格式,而-acodec指定音频编解码器。在这里,两者都指向s16le,可能是因为某些情况下需要同时指定以确保输出格式正确。但有时候可能只需要其中一个,不过同时使用可以避免ffmpeg自动选择其他格式。 另外,用户可能不知道输出文件的结构。PCM文件没有头信息,所以需要知道采样率、声道数、位深度等信息才能正确使用。比如,如果input.mp344.1kHz立体声,那么输出的PCM数据也是同样的参数,但用户需要自己记录这些信息,因为.pcm文件不包含元数据。 可能用户的应用场景是音频编程,比如生成波形、进行数字信号处理,或者与嵌入式系统通信,这些情况下需要原始的PCM数据。所以解释的时候需要提到这些使用场景,帮助他们理解为什么需要这样的转换。 还要注意是否有常见错误,比如忘记指定字节序(小端),或者参数顺序的问题。比如,-f和-acodec的位置是否正确,但ffmpeg通常对参数顺序不敏感,只要在输入和输出之前正确的位置即可。 总结来说,用户需要明白这个命令的作用是将压缩的MP3转换为原始的PCM格式,参数的具体含义,以及输出的PCM文件如何被正确使用。可能需要补充说明如何确定采样率、声道数,或者如何播放/处理生成的PCM文件,比如使用Audacity导入时需要设置正确的参数。</think>你提供的命令是一个使用 FFmpegMP3 音频文件转换为原始 PCM 音频数据的示例。以下是逐部分解释: --- ### 完整命令: ```bash ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le output.pcm ``` --- ### 参数解析: 1. **`-i input.mp3`** - `-i` 表示输入文件(Input)。 - `input.mp3` 是输入的 MP3 音频文件路径。 2. **`-f s16le`** - `-f` 指定输出格式(Format)。 - `s16le` 表示输出的 PCM 数据格式为 **有符号 16 位小端字节序**(signed 16-bit little-endian)。 - 这是原始音频的常见格式,未压缩,直接存储采样点的振幅值。 3. **`-acodec pcm_s16le`** - `-acodec` 指定音频编解码器(Audio Codec)。 - `pcm_s16le` 强制使用 PCM 16 位小端编码器,与 `-f s16le` 配合使用确保格式一致。 4. **`output.pcm`** - 输出的 PCM 文件路径。 --- ### 关键特性: - **PCM 是什么?** PCM(Pulse Code Modulation)是未经压缩的原始音频数据,直接存储每个采样点的数值。与 MP3(有损压缩格式)不同,PCM 文件体积更大,但保留了完整的音频信息。 - **输出文件特点**: - 文件没有封装格式(如 WAV 的头部信息),仅包含原始二进制音频数据- 需自行记录采样率、声道数等信息(默认与原 MP3 一致,可用 `ffprobe input.mp3` 查看)。 - **常见用途**: - 音频信号处理(如滤波、频谱分析)。 - 嵌入式设备音频播放(需原始 PCM 数据)。 - 语音识别等需要直接访问原始数据的场景。 --- ### 示例补充: 如果输入 MP3 是 **44.1kHz 采样率、双声道**,则输出的 PCM 数据会以以下形式存储: ``` [左声道采样点 16-bit][右声道采样点 16-bit][左声道...][右声道...]... ``` 每个采样点占用 2 字节(16 位),按小端字节序排列。 --- ### 验证与播放: 1. **查看 PCM 信息**: ```bash ffprobe -f s16le -ar 44100 -ac 2 output.pcm ``` 2. **用 FFmpeg 播放 PCM**: ```bash ffplay -f s16le -ar 44100 -ac 2 output.pcm ``` --- 通过这个命令,你可以将压缩的 MP3 转换为通用的原始音频格式,适用于需要直接处理音频底层数据的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值