采集音頻并對音頻編碼
文章對應(yīng)的項(xiàng)目地址
https://github.com/cuiyaoDroid/AndroidFFmpegAac
ffmpeg編譯靜態(tài)庫的部分只有編譯腳本修壕,ffmpeg的源碼可以從官方獲取。
一咧最、使用ndk編譯ffmpeg
獲取ffmpeg源碼。
git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg
然后我們修改一下里面的configure文件御雕,讓我們編譯出來的文件不會(huì)帶有奇怪的名字矢沿,不被Android識別。只要把
SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB) "$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'
SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR) $(SLIBNAME)'
修改成
SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'
LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'
SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'
SLIB_INSTALL_LINKS='$(SLIBNAME)'
我編譯時(shí)使用的腳本酸纲。需要根據(jù)自己的ndk的路徑配置交叉編譯腳本捣鲸。
android_config_armeabi_v7a.sh,此腳本需要是編譯armv7版本的靜態(tài)庫使用闽坡,編譯armv64版本需要修改腳本栽惶。
#!/bin/bash
NDK=/Users/yaocui/Documents/adt-bundle-mac-x86_64-20140702/android-ndk-r13b
#NDK=/Users/yaocui/Documents/adt-bundle-mac-x86_64-20140702/sdk/ndk-bundle
SYSROOT=$NDK/platforms/android-21/arch-arm64
TOOLCHAIN=$NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64
#TOOLCHAIN=$NDK/prebuilt/darwin-x86_64
function build_one {
./configure \
--prefix=$PREFIX \
--cc=$TOOLCHAIN/bin/aarch64-linux-android-gcc \
--nm=$TOOLCHAIN/bin/aarch64-linux-android-nm \
--enable-asm \
--enable-neon \
--enable-static \
--disable-shared \
--disable-doc \
--disable-asm \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-postproc \
--disable-avdevice \
--disable-symver \
--disable-stripping \
--disable-muxers \
--disable-encoders \
--enable-encoder=aac \
--disable-decoders \
--enable-decoder=aac \
--disable-demuxers \
--enable-demuxer=aac \
--disable-parsers \
--enable-parser=aac \
--cross-prefix=$TOOLCHAIN/bin/aarch64-linux-android- \
--target-os=linux \
--arch=aarch64 \
--cpu=armv8-a \
--enable-runtime-cpudetect \
--enable-gpl \
--enable-small \
--enable-cross-compile \
--sysroot=$SYSROOT \
--extra-cflags="-fPIC -DANDROID -I$NDK/platforms/android-21/arch-arm64/usr/include -I$SYSROOT/usr/include" \
--extra-ldflags="$ADDI_LDFLAGS"
sed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.h
sed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.h
sed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.h
sed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.h
sed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.h
sed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.h
sed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.h
sed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.h
sed -i '' 's/HAVE_LOG2 1/HAVE_LOG2 0/g' config.h
sed -i '' 's/HAVE_LOG2F 1/HAVE_LOG2F 0/g' config.h
make clean
make -j4
make install
}
#CPU=arm
#PREFIX=$(pwd)/android/$CPU
# arm v7vfp
CPU=armv8-a
OPTIMIZE_CFLAGS="-marm -march=$CPU "
PREFIX=./android/$CPU
ADDI_CFLAGS="-marm"
build_one
關(guān)鍵幾個(gè)腳本
--disable-shared --enable-static 關(guān)閉動(dòng)態(tài)庫的生成,開啟靜態(tài)庫的生成疾嗅,本例中使用靜態(tài)庫外厂,想要使用動(dòng)態(tài)庫也可以,但動(dòng)態(tài)庫最終生成的程序安裝包會(huì)相對大一些代承。
--disable-encoders --disable-decoders... 關(guān)閉所有編碼器解碼器等汁蝶,將無用的功能關(guān)閉掉,減小生成的庫文件的大小论悴。
--enable-encoder=aac --enable-decoder=aac 開啟aac的編碼器和解碼器掖棉,我們只用到了aac的編解碼器。
--disable-ffmpeg --disable-ffplay --disable-ffprobe --disable-ffserver --disable-postproc --disable-avdevice 關(guān)閉一些我們不需要的功能意荤,減小庫文件大小啊片。
執(zhí)行編譯腳本
./android_config_armeabi_v7a.sh
生成的庫文件在android目錄下。
arm64的編譯過程一樣玖像,可以查看我的項(xiàng)目。
https://github.com/cuiyaoDroid/AndroidFFmpegAac/blob/master/android_config_arm64_v8a.sh
二齐饮、創(chuàng)建android項(xiàng)目
1捐寥、創(chuàng)建一個(gè)android項(xiàng)目,添加c++支持祖驱。
2握恳、將編譯ffmpeg生成的靜態(tài)庫和頭文件復(fù)制進(jìn)項(xiàng)目目錄下。
3捺僻、編寫Application.mk乡洼,支持armeabi-v7a arm64-v8a兩種架構(gòu)崇裁。
APP_STL := gnustl_static
APP_LDFLAGS := -latomic
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-21
4、編寫Android.mk文件束昵,根據(jù)不同的cpu架構(gòu)使用不同的靜態(tài)庫拔稳。
LOCAL_PATH := $(call my-dir)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avcodec
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libavcodec.a
else
LOCAL_SRC_FILES := libarm64/libavcodec.a
endif
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avfilter
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libavfilter.a
else
LOCAL_SRC_FILES := libarm64/libavfilter.a
endif
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avformat
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libavformat.a
else
LOCAL_SRC_FILES := libarm64/libavformat.a
endif
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := avutil
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libavutil.a
else
LOCAL_SRC_FILES := libarm64/libavutil.a
endif
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := swresample
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libswresample.a
else
LOCAL_SRC_FILES := libarm64/libswresample.a
endif
include $(PREBUILT_STATIC_LIBRARY)
# prepare libX
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_MODULE := swscale
ifeq ($(APP_ABI), armeabi-v7a)
LOCAL_SRC_FILES := libarmv7a/libswscale.a
else
LOCAL_SRC_FILES := libarm64/libswscale.a
endif
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
TARGET_ARCH_ABI := armeabi-v7a arm64-v8a
LOCAL_MODULE := ffmpeg_aac_jni
LOCAL_SRC_FILES := FFmpegAacJni.cpp AacRecoder.cpp AacPlayer.cpp
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_CFLAGS := -D__STDC_CONSTANT_MACROS -Wno-sign-compare -Wno-switch -Wno-pointer-sign -DHAVE_NEON=1 -mfpu=neon -mfloat-abi=softfp -fPIC -DANDROID
LOCAL_STATIC_LIBRARIES := avfilter avformat avcodec swresample swscale avutil
LOCAL_LDLIBS := -L$(NDK_ROOT)/platforms/$(APP_PLATFORM)/arch-arm/usr/lib -L$(LOCAL_PATH) -llog -ljnigraphics -landroid -lz -ldl -lm
include $(BUILD_SHARED_LIBRARY)
腳本中使用LOCAL_STATIC_LIBRARIES配置靜態(tài)庫的鏈接,對順序有要求锹雏,不同版本ffmpeg的順序可能不同巴比,可以查看ffmpeg目錄下的makefile文件,查看庫文件加載順序礁遵。
...
# $(FFLIBS-yes) needs to be in linking order
FFLIBS-$(CONFIG_AVDEVICE) += avdevice
FFLIBS-$(CONFIG_AVFILTER) += avfilter
FFLIBS-$(CONFIG_AVFORMAT) += avformat
FFLIBS-$(CONFIG_AVCODEC) += avcodec
FFLIBS-$(CONFIG_AVRESAMPLE) += avresample
FFLIBS-$(CONFIG_POSTPROC) += postproc
FFLIBS-$(CONFIG_SWRESAMPLE) += swresample
FFLIBS-$(CONFIG_SWSCALE) += swscale
FFLIBS := avutil
...
5轻绞、創(chuàng)建c++文件實(shí)現(xiàn)編解碼。
三佣耐、編解碼邏輯的實(shí)現(xiàn)政勃。
1、首先創(chuàng)建java的native方法兼砖,這里我們一共需要6個(gè)方法奸远,分別是
編碼部分:初始化編碼器、編碼pcm數(shù)據(jù)得到aac數(shù)據(jù)掖鱼、關(guān)閉編碼器然走。
解碼部分:初始化解碼器、使用解碼器讀取pcm數(shù)據(jù)戏挡、關(guān)閉解碼器芍瑞。
public class FFmpegAacNativeLib
{
public long mNativeContextRecoder;
public long mNativeContextPlayer;
static {
System.loadLibrary("ffmpeg_aac_jni");
}
//declare the jni functions
public native String audioPlayerOpenFile(String path);//初始化解碼器
public native int audioPlayerGetPCM(byte[] pcmbuffer);//使用解碼器讀取pcm數(shù)據(jù)
public native int audioPlayerStop();//關(guān)閉解碼器
public native int audioEncodePCMToAACInit();//初始化編碼器
public native int audioEncodePCMToAAC(byte[] pcmbuf,int len,byte[] amrbuf);//使用解碼器讀取pcm數(shù)據(jù)
public native void audioEncodeStop();//關(guān)閉解碼器
//Singleton
private static FFmpegAacNativeLib instance=null;
public static FFmpegAacNativeLib getInstance() {
if(instance==null)
instance=new FFmpegAacNativeLib();
return instance;
}
}
2、編寫jni接口褐墅。
FFmpegAacJni.cpp
...
#include <jni.h>
...
static jint audioEncodePCMToAACInit(JNIEnv *env, jobject thiz) {
return 0;
}
static jint audioEncodePCMToAAC(JNIEnv *env, jobject thiz, jbyteArray pcmbuf_,jint len,
jbyteArray amrbuf_) {
return 0;
}
static void audioEncodeStop(JNIEnv *env, jobject thiz) {
}
static jstring audioPlayerOpenFile(JNIEnv *env, jobject thiz, jstring path) {
return NULL;
}
static jint audioPlayerGetPCM(JNIEnv *env, jobject thiz, jbyteArray pcmbuffer_) {
return 0;
}
static jint audioPlayerStop(JNIEnv *env, jobject thiz) {
// TODO
return 0;
}
static JNINativeMethod gMethods[] = {
{ "audioEncodePCMToAACInit", "()I", (void *)audioEncodePCMToAACInit },
{ "audioEncodePCMToAAC", "([BI[B)I", (void *)audioEncodePCMToAAC },
{ "audioEncodeStop", "()V", (void *)audioEncodeStop },
{ "audioPlayerOpenFile", "(Ljava/lang/String;)Ljava/lang/String;", (void *)audioPlayerOpenFile },
{ "audioPlayerGetPCM", "([B)I", (void *)audioPlayerGetPCM },
{ "audioPlayerStop", "()I", (void *)audioPlayerStop }
};
jint JNI_OnLoad(JavaVM* vm, void* reserved){
ALOGD("JNI_OnLoad");
JNIEnv* env = NULL;
jint result = -1;
jclass clazz;
if ((*vm).GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("GetEnv fail");
return result;
}
assert(env != NULL);
clazz = (*env).FindClass("com/cuiyao/ffmpegaac/lib/FFmpegAacNativeLib");
if (clazz == NULL) {
ALOGE("com/cuiyao/ffmpegaac/lib/FFmpegAacNativeLib not found");
return result;
}
// 注冊native方法到j(luò)ava中
if ((*env).RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) {
ALOGE("RegisterNatives methods fail");
return result;
}
// 返回jni的版本
return JNI_VERSION_1_4;
}
3拆檬、關(guān)鍵代碼編碼器初始化,設(shè)置目標(biāo)音頻各項(xiàng)參數(shù)妥凳,初始化編碼器和緩沖區(qū)竟贯。在后面編寫java層AudioRecord的參數(shù)和緩存區(qū)需要一一對應(yīng)。
ALOGW("start");
// av_register_all();
avcodec_register_all();
mAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);//查找AAC編碼器
if(!mAVCodec){
ALOGE("encoder AV_CODEC_ID_AAC not found");
return -1;
}
mAVCodecContext = avcodec_alloc_context3(mAVCodec);
if(mAVCodecContext != NULL){
mAVCodecContext->codec_id = AV_CODEC_ID_AAC;
mAVCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
mAVCodecContext->bit_rate = 12200;
mAVCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
mAVCodecContext->sample_rate = 8000;
mAVCodecContext->channel_layout = AV_CH_LAYOUT_MONO;
mAVCodecContext->channels = av_get_channel_layout_nb_channels(mAVCodecContext->channel_layout);
}else {
ALOGE("avcodec_alloc_context3 fail");
return -1;
}
ALOGW("start 3 channels %d",mAVCodecContext->channels);
if(avcodec_open2(mAVCodecContext, mAVCodec, NULL) < 0){
ALOGE("aac avcodec open fail");
av_free(mAVCodecContext);
mAVCodecContext = NULL;
return -1;
}
mAVFrame = av_frame_alloc();
if(!mAVFrame) {
ALOGE("avframe alloc fail");
avcodec_close(mAVCodecContext);
av_free(mAVCodecContext);
mAVCodecContext = NULL;
return -1;
}
mAVFrame->nb_samples = mAVCodecContext->frame_size;
mAVFrame->format = mAVCodecContext->sample_fmt;
mAVFrame->channel_layout = mAVCodecContext->channel_layout;
mBufferSize = av_samples_get_buffer_size(NULL, mAVCodecContext->channels, mAVCodecContext->frame_size, mAVCodecContext->sample_fmt, 0);
if(mBufferSize < 0){
ALOGE("av_samples_get_buffer_size fail");
av_frame_free(&mAVFrame);
mAVFrame = NULL;
avcodec_close(mAVCodecContext);
av_free(mAVCodecContext);
mAVCodecContext = NULL;
return -1;
}
mEncoderData = (uint8_t *)av_malloc(mBufferSize);
if(!mEncoderData){
ALOGE("av_malloc fail");
av_frame_free(&mAVFrame);
mAVFrame = NULL;
avcodec_close(mAVCodecContext);
av_free(mAVCodecContext);
mAVCodecContext = NULL;
return -1;
}
avcodec_fill_audio_frame(mAVFrame, mAVCodecContext->channels, mAVCodecContext->sample_fmt, (const uint8_t*)mEncoderData, mBufferSize, 0);
4逝钥、使用編碼器對pcm數(shù)據(jù)進(jìn)行編碼得到aac數(shù)據(jù)
int AacRecoder::encode_pcm_data(void* pIn, int frameSize,jbyte * pOut){
int encode_ret = -1;
int got_packet_ptr = 0;
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
if(mAVCodecContext && mAVFrame){
short2float((int16_t *)pIn, mEncoderData, frameSize/2);
mAVFrame->data[0] = mEncoderData;
mAVFrame->pts = 0;
//音頻編碼
encode_ret = avcodec_encode_audio2(mAVCodecContext, &pkt, mAVFrame, &got_packet_ptr);
if(encode_ret < 0){
ALOGE("Failed to encode!\n");
return encode_ret;
}
if(pkt.size > 0){
int length = pkt.size + ADTS_HEADER_LENGTH;
void *adts = malloc(ADTS_HEADER_LENGTH);
//添加adts header 可以正常播放屑那。
addADTSheader((uint8_t *)adts, pkt.size+ADTS_HEADER_LENGTH);
// ALOGW("header ---- =%s",adts);
memcpy(pOut,adts, ADTS_HEADER_LENGTH);
free(adts);
memcpy(pOut+ADTS_HEADER_LENGTH,pkt.data,pkt.size);
// ALOGW("data ---- =%s",pkt.data);
av_free_packet(&pkt);
return length;
}
av_free_packet(&pkt);
return 0;
}
return encode_ret;
}
對每一幀的音頻加adts頭,不加adts頭的音頻無法直接播放艘款。
void AacRecoder::addADTSheader(uint8_t * in, int packet_size){
int sampling_frequency_index = 11; //采樣率下標(biāo)
int channel_configuration = mAVCodecContext->channels; //聲道數(shù)
in[0] = 0xFF;
in[1] = 0xF9;
in[2] = 0x40 | (sampling_frequency_index << 2) | (channel_configuration >> 2);//0x6c;
in[3] = (channel_configuration & 0x3) << 6;
in[3] |= (packet_size & 0x1800) >> 11;
in[4] = (packet_size & 0x1FF8) >> 3;
in[5] = ((((unsigned char)packet_size) & 0x07) << 5) | (0xff >> 3);
in[6] = 0xFC;
}
5持际、解碼器初始化,得到音頻的音道數(shù)量和采樣率哗咆。
ALOGW("start");
// myinput = (char*)malloc(sizeof(input));
// strcpy(myinput,input);
ALOGI("%s", "sound");
//注冊組件
av_register_all();
pFormatCtx = avformat_alloc_context();
//打開音頻文件
if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {
ALOGI("%s", "無法打開音頻文件");
return NULL;
}
//獲取輸入文件信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
ALOGI("%s", "無法獲取輸入文件信息");
return NULL;
}
//獲取音頻流索引位置
int i = 0;
for (; i < pFormatCtx->nb_streams; i++) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
audio_stream_idx = i;
break;
}
}
//獲取解碼器
codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);
if (codec == NULL) {
ALOGI("%s", "無法獲取解碼器");
return NULL;
}
//打開解碼器
if (avcodec_open2(codecCtx, codec, NULL) < 0) {
ALOGI("%s", "無法打開解碼器");
return NULL;
}
//壓縮數(shù)據(jù)
//解壓縮數(shù)據(jù)
frame = av_frame_alloc();
//frame->16bit 44100 PCM 統(tǒng)一音頻采樣格式與采樣率
swrCtx = swr_alloc();
//重采樣設(shè)置參數(shù)-------------start
//輸入的采樣格式
enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;
//輸出采樣格式16bit PCM
out_sample_fmt = AV_SAMPLE_FMT_S16;
//輸入采樣率
int in_sample_rate = codecCtx->sample_rate;
//輸出采樣率
int out_sample_rate = in_sample_rate;
//獲取輸入的聲道布局
//根據(jù)聲道個(gè)數(shù)獲取默認(rèn)的聲道布局(2個(gè)聲道蜘欲,默認(rèn)立體聲stereo)
//av_get_default_channel_layout(codecCtx->channels);
uint64_t in_ch_layout = codecCtx->channel_layout;
//輸出的聲道布局(立體聲)
uint64_t out_ch_layout = AV_CH_LAYOUT_MONO;
swr_alloc_set_opts(swrCtx,
out_ch_layout, out_sample_fmt, out_sample_rate,
in_ch_layout, in_sample_fmt, in_sample_rate,
0, NULL);
ALOGI("in_samplde_rate %d",in_sample_rate);
swr_init(swrCtx);
//輸出的聲道個(gè)數(shù)
out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
sprintf(info,"%d,%d",in_sample_rate,out_channel_nb);
6、得到解碼后的pcm數(shù)據(jù)晌柬。
int AacPlayer::getpcmbuff(uint8_t* out_buffer){
//16bit 44100 PCM 數(shù)據(jù)
AVPacket packet;
av_init_packet(&packet);
int got_frame = 0, index = 0, ret;
int out_buffer_size = 0;
//不斷讀取壓縮數(shù)據(jù)
if(av_read_frame(pFormatCtx, &packet) >= 0){
//解碼音頻類型的Packet
if (packet.stream_index == audio_stream_idx) {
//解碼
ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, &packet);
if (ret < 0) {
ALOGI("%s", "解碼完成");
}
//解碼一幀成功
if (got_frame > 0) {
ALOGI("解碼:%d", index++);
swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE,
(const uint8_t **) frame->data, frame->nb_samples);
//獲取sample的size
out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb,
frame->nb_samples, out_sample_fmt,
1);
}
}
}else{
av_free_packet(&packet);
return -1;
}
av_free_packet(&packet);
return out_buffer_size;
}
這部分只貼出了關(guān)鍵的編解碼的代碼姥份,使用c++編寫aac編解碼部分的代碼郭脂,并通過jni接口向java層提供編解碼方法,配合java中的AudioRecord和AudioTrack即可以實(shí)現(xiàn)錄音和播放澈歉。
四展鸡、錄音和播放的實(shí)現(xiàn)
這部分是使用AudioRecord和AudioTrack配合已經(jīng)寫好的native方法進(jìn)行錄音和播放的實(shí)現(xiàn)。
1闷祥、錄音的流程
初始化AudioRecord和編碼器—>采集pcm音頻數(shù)據(jù)—>使用編碼器進(jìn)行編碼—>將編碼后的數(shù)據(jù)寫入文件—>關(guān)閉AudioRecord和編碼器
2娱颊、播放流程是兩條線
初始化解碼器加載文件—>解碼文件讀取解碼后的pcm數(shù)據(jù)—>將解碼后的數(shù)據(jù)加入播放緩沖區(qū)—>關(guān)閉解碼器
初始化AudioTrack—>讀取緩沖區(qū)中的pcm數(shù)據(jù)—>播放—>關(guān)閉AudioTrack
這一部分代碼就不貼出來了,如果想看具體的實(shí)現(xiàn)可以到我的項(xiàng)目凯砍,里面有完整的aac音頻錄制和播放的demo箱硕。
項(xiàng)目地址
https://github.com/cuiyaoDroid/AndroidFFmpegAac