AAC 還原 pcm/WAV, faad 的已知問題填坑

这篇博客记录了在处理AAC流媒体时遇到的问题,特别是关于libfaad库将AAC转换为PCM时会将mono音频误转为stereo的错误。作者提供了Makefile中包含FAAD的示例,并分享了如何通过ffplay进行播放测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

年假在家研究一了一下流媒體,之前客戶需求要把mp4轉成h264+pcm 串流,紀錄一下

前言

libfaac 相信轉過aac格式的人都知道。他有一個對應的庫:libfaad,可用於將aac轉為pcm,windows , linux , macOS 都有offcial release,其他os 可能要交叉編譯就是了。

libfaac & libfaad 官方頁面.

WAV 檔實際上就是 wav header (72 byte)+ pcm source,方便播放&debug 就faad decode pcm前面加上了

詳細介紹wav header.

FAAD (libfaad)的已知錯誤

非常坑爹
除非從原碼階段自行編譯,否則AAC mono一律會變成stereo,網路上已有先見詳述原因與解法

原碼

Makefile 要怎樣include faad

CC = gcc
LIB= -lfaad
INC = neaacdec.h faad.h
SRC =
SIMPLE_MAIN = aac_decode.c
MAIN = aac_decode.c
TARGET = aac_decoder
LDFLAG = -pthread
CFLAG = $(SRC) $(MAIN) $(INC)  $(LDFLAG) $(LIB) 

all:
	$(CC) $(CFLAG) -o $(TARGET) 
	mv $(TARGET) ./bin
	@echo "make all"

clean:
	rm -f bin/*
	@echo "clean all"

faad example

/*
 * Example file that decodes raw AAC data from stdin,
 * and outputs the PCM audio data to stdout.
 */

#include <neaacdec.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct WavFileHeader
{
    char        id[4];          // should always contain "RIFF"
    int     totallength;    // total file length minus 8
    char        wavefmt[8];     // should be "WAVEfmt "
    int     format;         // 16 for PCM format
    short     pcm;            // 1 for PCM format
    short     channels;       // channels
    int     frequency;      // sampling frequency
    int     bytes_per_second;
    short     bytes_by_capture;
    short     bits_per_sample;
    char        data[4];        // should always contain "data"
    int     bytes_in_data;
};
void write_wav_header(FILE* file, int totalsamcnt_per_channel, int samplerate, int channels){
    struct WavFileHeader filler;
    strcpy(filler.id, "RIFF");
    filler.bits_per_sample = 16;
    filler.totallength = (totalsamcnt_per_channel * channels * filler.bits_per_sample/8) + sizeof(filler) - 8; //81956
    strcpy(filler.wavefmt, "WAVEfmt ");
    filler.format = 16;
    filler.pcm = 1;
    filler.channels = channels;
    filler.frequency = samplerate;
    filler.bytes_per_second = filler.channels * filler.frequency * filler.bits_per_sample/8;
    filler.bytes_by_capture = filler.channels*filler.bits_per_sample/8;
    filler.bytes_in_data = totalsamcnt_per_channel * filler.channels * filler.bits_per_sample/8;    
    strcpy(filler.data, "data");
    fwrite(&filler, 1, sizeof(filler), file);
}

void help()
{
	printf("[Usage]:\r\n"
				"-i <input file> in AAC format\r\n"
				"-o <output file> in WAV format\r\n");
	return;
}

int main (int argc, char* argv[]) 
{
	int c;
 	char* aacfile = "sample.aac";
	char* wavename = "out.wav";
	while ((c = getopt(argc, argv, "i:o:h" ) ) != -1) {
		switch (c) {
			case 'i':
				aacfile = optarg;
				break;
			case 'o':
				wavename = optarg;
				break;
			case 'h':
				help();
				exit(1);
			default:
				help();
				exit(1);
		}
	}
	printf("hello aac decoder\r\n");
	NeAACDecHandle faadhandle = NeAACDecOpen();
	if (faadhandle == NULL) {
		fprintf(stderr, "failed to open aac decode handle\r\n");
	}

	NeAACDecConfigurationPtr conf = NeAACDecGetCurrentConfiguration(faadhandle);
	if (conf == NULL) {
		fprintf(stderr, "failed to get decoder conf\r\n");
	}
	
	conf->defObjectType = LC;
	//conf->defSampleRate = 8000;  not sure every time
	conf->outputFormat = FAAD_FMT_16BIT ; 

	NeAACDecSetConfiguration(faadhandle, conf);

	FILE* file = fopen(aacfile, "rb");
	if (file == NULL) {
		fprintf(stderr, "failed to open input file\r\n");
	}
	fseek(file, 0, SEEK_END);
	long filelen = ftell(file);
	fseek(file, 0, SEEK_SET);
	unsigned char* filebuf = (unsigned char*)malloc(filelen);

	if (filebuf == NULL) {
		fprintf(stderr, "failed to alloc\r\n");
	}
	int len = fread(filebuf, 1, filelen, file);
	fclose(file);

	unsigned long samplerate = 0;
	unsigned char channel = 0;
	int ret = NeAACDecInit(faadhandle, filebuf, len, &samplerate, &channel);

	if (ret > 0) {
		fprintf(stderr, "failed to init AACDecoder\r\n");
	}

	printf("aacinit ok: sam=%lu, chn=%d\n", samplerate, channel);
	NeAACDecFrameInfo frameinfo;
	unsigned char* curbyte = filebuf;
	unsigned long leftsize = len;
	
	FILE* wavfile = fopen(wavename, "wb");
	if (wavfile == NULL) {
		fprintf(stderr, "failed to open output file\r\n");
	}

	int wavheadsize = sizeof(struct WavFileHeader);
	fseek(wavfile, wavheadsize, SEEK_SET);
	int totalsmp_per_chl = 0;
	void* out = NULL;

	while (out = NeAACDecDecode(faadhandle, &frameinfo, curbyte, leftsize)) {
#if(0)
		printf("decode one frame ok: sam:%ld, chn=%d, samplecount=%ld, obj_type=%d, header_type=%d, consumed=%ld\n",
				frameinfo.samplerate, frameinfo.channels, frameinfo.samples, frameinfo.object_type,
				frameinfo.header_type, frameinfo.bytesconsumed);
#endif
		curbyte += frameinfo.bytesconsumed;
		leftsize -= frameinfo.bytesconsumed;
		fwrite(out, 1, frameinfo.samples*2, wavfile); // frameinfo.samples is all channel sample sum in 16bits
		totalsmp_per_chl += frameinfo.samples / frameinfo.channels;
	}
	printf("aac decode done, totalsmp_per_chl=%d\n", totalsmp_per_chl);
	fseek(wavfile, 0, SEEK_SET);
	write_wav_header(wavfile, totalsmp_per_chl, (int)samplerate, (int)channel);
	fclose(wavfile);

	NeAACDecClose(faadhandle);
	return 0;

}

親測

$ mkdir bin
$ make clean; make
$ cd  bin
$ ./aac_decoder -i <input file name> [-o <output  file name>]

使用ffplay 播放

agathakuan@agathakuan-ThinkPad-T460:~/文件/AAC_decode$ make clean
rm -f bin/*
clean all
agathakuan@agathakuan-ThinkPad-T460:~/文件/AAC_decode$ make
gcc  aac_decode.c neaacdec.h faad.h  -pthread -lfaad  -o aac_decoder 
faad.h:32:9: note: #pragma message: please update faad2 include filename and function names!
 #pragma message("please update faad2 include filename and function names!")
         ^~~~~~~
mv aac_decoder ./bin
cp sample.aac bin/
make all
agathakuan@agathakuan-ThinkPad-T460:~/文件/AAC_decode$ cd bin/
agathakuan@agathakuan-ThinkPad-T460:~/文件/AAC_decode/bin$ ./aac_decoder -i sample.aac  -o sample.wav
hello aac decoder
aacinit ok: sam=44100, chn=2
aac decode done, totalsmp_per_chl=10765312
agathakuan@agathakuan-ThinkPad-T460:~/文件/AAC_decode/bin$ ffplay -i sample.wav ffplay version 3.4.8-0ubuntu0.2 Copyright (c) 2003-2020 the FFmpeg developers
  built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
  configuration: --prefix=/usr --extra-version=0ubuntu0.2 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
  libavutil      55. 78.100 / 55. 78.100
  libavcodec     57.107.100 / 57.107.100
  libavformat    57. 83.100 / 57. 83.100
  libavdevice    57. 10.100 / 57. 10.100
  libavfilter     6.107.100 /  6.107.100
  libavresample   3.  7.  0 /  3.  7.  0
  libswscale      4.  8.100 /  4.  8.100
  libswresample   2.  9.100 /  2.  9.100
  libpostproc    54.  7.100 / 54.  7.100
Input #0, wav, from 'sample.wav':  0KB vq=    0KB sq=    0B f=0/0   
  Duration: 00:04:04.11, bitrate: 1411 kb/s
    Stream #0:0: Audio: pcm_s16le ([1][0][0][0] / 0x0001), 44100 Hz, 2 channels, s16, 1411 kb/s
Switch subtitle stream from #-1 to #-1 vq=    0KB sq=    0B f=0/0   
   2.85 M-A: -0.000 fd=   0 aq=  180KB vq=    0KB sq=    0B f=0/0   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值