年假在家研究一了一下流媒體,之前客戶需求要把mp4轉成h264+pcm 串流,紀錄一下
前言
libfaac 相信轉過aac格式的人都知道。他有一個對應的庫:libfaad,可用於將aac轉為pcm,windows , linux , macOS 都有offcial release,其他os 可能要交叉編譯就是了。
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