android使用ffmpeg錄制和播放aac文件

采集音頻并對音頻編碼
文章對應(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市悟衩,隨后出現(xiàn)的幾起案子剧罩,更是在濱河造成了極大的恐慌,老刑警劉巖座泳,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惠昔,死亡現(xiàn)場離奇詭異,居然都是意外死亡挑势,警方通過查閱死者的電腦和手機(jī)镇防,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮饱,“玉大人来氧,你說我怎么就攤上這事∠憷” “怎么了啦扬?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凫碌。 經(jīng)常有香客問我扑毡,道長,這世上最難降的妖魔是什么盛险? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任瞄摊,我火速辦了婚禮,結(jié)果婚禮上苦掘,老公的妹妹穿的比我還像新娘泉褐。我一直安慰自己,他們只是感情好鸟蜡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挺邀,像睡著了一般揉忘。 火紅的嫁衣襯著肌膚如雪跳座。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天泣矛,我揣著相機(jī)與錄音疲眷,去河邊找鬼。 笑死您朽,一個(gè)胖子當(dāng)著我的面吹牛狂丝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哗总,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼几颜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讯屈?” 一聲冷哼從身側(cè)響起蛋哭,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎涮母,沒想到半個(gè)月后谆趾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叛本,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年沪蓬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片来候。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跷叉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吠勘,到底是詐尸還是另有隱情性芬,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布剧防,位于F島的核電站植锉,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏峭拘。R本人自食惡果不足惜俊庇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鸡挠。 院中可真熱鬧辉饱,春花似錦、人聲如沸拣展。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽备埃。三九已至姓惑,卻和暖如春褐奴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背于毙。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工敦冬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唯沮。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓脖旱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親介蛉。 傳聞我的和親對象是個(gè)殘疾皇子萌庆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 1.FFmepg編譯環(huán)境及結(jié)構(gòu) 下載FFmepg FFmpeg配置選項(xiàng)介紹 下載gas-preprocessor....
    Jackey_song閱讀 2,209評論 2 2
  • 1. 安裝編譯環(huán)境 2. 下載源碼 3. 配置選項(xiàng) 通過configure來配置需要編譯的ffmpeg庫 3.1 ...
    Weller0閱讀 1,329評論 2 4
  • 前幾天項(xiàng)目需要壓縮視頻,Github上找了許多庫甘耿,要么就是太大踊兜,要么就是質(zhì)量不高,其實(shí)我只需要壓縮視頻佳恬,最好的方案...
    voiddog閱讀 26,231評論 59 82
  • 熟悉命令之后惨奕,自然是對其根據(jù)自己的需求進(jìn)行應(yīng)用了珠增。所以久等的第三編文章就來放放水钠糊。記錄一下在Android端的集成...
    deep_sadness閱讀 1,533評論 0 4
  • 不知為什么學(xué)起花藝來杉女。可能是命里的一種緣分倾剿。 人與人之間的緣分常常妙不可言筷频,人與物之間也有時(shí)如此。 小時(shí)候有過很多...
    辰亦風(fēng)閱讀 1,480評論 0 0