十七蛮粮、音頻轉(zhuǎn)PCM和使用native調(diào)用AudioTrack播放音頻文件

一、Android AudioTrack簡(jiǎn)介

在Android中播放音頻可以用MediaPlayer和AudioTrack兩種方案的谜慌,但是兩種方案是有很大區(qū)別的然想,MediaPlayer可以播放多種格式的聲音文件,例如MP3欣范,AAC变泄,WAV,OGG恼琼,MIDI等妨蛹。而AudioTrack只能播放PCM數(shù)據(jù)流。
事實(shí)上晴竞,兩種本質(zhì)上是沒啥區(qū)別的蛙卤,MediaPlayer在播放音頻時(shí),在framework層還是會(huì)創(chuàng)建AudioTrack噩死,把解碼后的PCM數(shù)流傳遞給AudioTrack表窘,最后由AudioFlinger進(jìn)行混音典予,傳遞音頻給硬件播放出來。利用AudioTrack播放只是跳過Mediaplayer的解碼部分而已乐严。Mediaplayer的解碼核心部分是基于OpenCORE 來實(shí)現(xiàn)的瘤袖,支持通用的音視頻和圖像格式,codec使用的是OpenMAX接口來進(jìn)行擴(kuò)展昂验。因此使用audiotrack播放mp3文件的話捂敌,要自己加入一個(gè)音頻解碼器,如libmad既琴。否則只能播放PCM數(shù)據(jù)占婉,如大多數(shù)WAV格式的音頻文件。
如果是實(shí)時(shí)的音頻數(shù)據(jù)甫恩,那么只能用AudioTrack進(jìn)行播放逆济。

音頻所占用字節(jié)數(shù) = 通道數(shù) * 采用頻率(Hz) * 采用位數(shù)(byte)

二、具體實(shí)現(xiàn)

native-lib.cpp

2.1音頻轉(zhuǎn)PCM和使用native調(diào)用AudioTrack播放音頻文件磺箕,代碼公共部分
#include <jni.h>
#include <string>
#include <android/log.h>

extern "C" {

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include <android/native_window_jni.h>
#include <unistd.h>

};

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"MusicPlayer",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"MusicPlayer",FORMAT,##__VA_ARGS__);

2.2 音頻轉(zhuǎn)PCM
extern "C"
JNIEXPORT void JNICALL
Java_com_fmtech_ffmpegmusic_MusicPlayer_audioToPcm(JNIEnv *env, jobject instance, jstring inputPath_, jstring outputPath_) {
    const char *inputPath = env->GetStringUTFChars(inputPath_, 0);
    const char *outputPath = env->GetStringUTFChars(outputPath_, 0);

    av_register_all();

    AVFormatContext *pFormatCtx = avformat_alloc_context();

    if(avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0){
        LOGE("Open input failed.");
        return;
    }

    if(avformat_find_stream_info(pFormatCtx, NULL) < 0){
        LOGE("Find stream info failed.");
        return;
    }

    int audio_stream_idx = -1;
    int i = 0;
    for(i = 0; i < pFormatCtx->nb_streams; i++){
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
            audio_stream_idx = i;
            break;
        }
    }

    AVCodecContext *pCodecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
        LOGE("avcodec_open2 failed.");
        return;
    }

    AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    AVFrame *frame = av_frame_alloc();

    SwrContext *swrContext = swr_alloc();

    int got_frame;

    uint8_t *out_buffer = (uint8_t*)av_malloc(44100 * 2);

    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;

    //輸出采樣位數(shù)
    enum AVSampleFormat out_sample_format = AV_SAMPLE_FMT_S16;

    //輸出采樣率必須與輸入相同
    int out_sample_rate = pCodecCtx->sample_rate;

    swr_alloc_set_opts(swrContext, out_ch_layout, out_sample_format, out_sample_rate,
                        pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);

    swr_init(swrContext);
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    LOGI("-------Out channecl nb:%d",out_channel_nb);

    FILE *pcm_file = fopen(outputPath, "wb");
    while(av_read_frame(pFormatCtx, packet) >= 0){
        if(packet->stream_index == audio_stream_idx){
            avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
            if(got_frame){
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_format, 1);
                fwrite(out_buffer, 1, out_buffer_size, pcm_file);
            }
        }
    }
    LOGI("-------Decode audio success.");

    fclose(pcm_file);
    av_frame_free(&frame);
    av_free(out_buffer);
    swr_free(&swrContext);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    env->ReleaseStringUTFChars(inputPath_, inputPath);
    env->ReleaseStringUTFChars(outputPath_, outputPath);
}
2.3使用native調(diào)用AudioTrack播放音頻文件
extern "C"
JNIEXPORT void JNICALL
Java_com_fmtech_ffmpegmusic_MusicPlayer_playMusic(JNIEnv *env, jobject instance,
                                                  jstring inputPath_) {
    const char *inputPath = env->GetStringUTFChars(inputPath_, 0);

    av_register_all();

    AVFormatContext *pFormatCtx = avformat_alloc_context();

    if(avformat_open_input(&pFormatCtx, inputPath, NULL, NULL) != 0){
        LOGE("Open input failed.");
        return;
    }

    if(avformat_find_stream_info(pFormatCtx, NULL) < 0){
        LOGE("Find stream info failed.");
        return;
    }

    int audio_stream_idx = -1;
    int i = 0;
    for(i = 0; i < pFormatCtx->nb_streams; i++){
        if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
            audio_stream_idx = i;
            break;
        }
    }

    AVCodecContext *pCodecCtx = pFormatCtx->streams[audio_stream_idx]->codec;
    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0){
        LOGE("avcodec_open2 failed.");
        return;
    }

    AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    AVFrame *frame = av_frame_alloc();

    SwrContext *swrContext = swr_alloc();

    int got_frame;

    uint8_t *out_buffer = (uint8_t*)av_malloc(44100 * 2);

    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;

    //輸出采樣位數(shù)
    enum AVSampleFormat out_sample_format = AV_SAMPLE_FMT_S16;

    //輸出采樣率必須與輸入相同
    int out_sample_rate = pCodecCtx->sample_rate;

    swr_alloc_set_opts(swrContext, out_ch_layout, out_sample_format, out_sample_rate,
                       pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0, NULL);

    swr_init(swrContext);
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    LOGI("-------Out channecl nb:%d",out_channel_nb);

   //調(diào)用MusicPlayer.java中的方法
    jclass clazzMusicPlayer = env->GetObjectClass(instance);
    jmethodID initAudioTrack = env->GetMethodID(clazzMusicPlayer, "initAudioTrack", "(II)V");
    jmethodID playTrack = env->GetMethodID(clazzMusicPlayer, "playTrack", "([BI)V");
    env->CallVoidMethod(instance, initAudioTrack, 44100, out_channel_nb);

    int frameCount=0;
    while(av_read_frame(pFormatCtx, packet) >= 0){
        if(packet->stream_index == audio_stream_idx){
            avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
            if(got_frame){
                LOGI("Decode %d frame.", frameCount++);
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_format, 1);

                jbyteArray audio_sample_array = env->NewByteArray(out_buffer_size);
                env->SetByteArrayRegion(audio_sample_array, 0, out_buffer_size, (const jbyte *) out_buffer);
                env->CallVoidMethod(instance, playTrack, audio_sample_array, out_buffer_size);
                env->DeleteLocalRef(audio_sample_array);
            }
        }
    }
    LOGI("-------Play audio finish.");

    av_frame_free(&frame);
    av_free(out_buffer);
    swr_free(&swrContext);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    env->ReleaseStringUTFChars(inputPath_, inputPath);
}

MusicPlayer.java

public class MusicPlayer {

    static{
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avformat-56");
        System.loadLibrary("avutil-54");
        System.loadLibrary("postproc-53");
        System.loadLibrary("swresample-1");
        System.loadLibrary("swscale-3");
        System.loadLibrary("native-lib");
    }

    private AudioTrack mAudioTrack;

    public void initAudioTrack(int sampleRateInHz, int nb_channels){

        int channelConfig;
        if(nb_channels == 1){
            channelConfig = AudioFormat.CHANNEL_OUT_MONO;//單聲道
        }else if(nb_channels == 2){
            channelConfig = AudioFormat.CHANNEL_OUT_STEREO;//雙聲道立體聲
        }else{
            channelConfig = AudioFormat.CHANNEL_OUT_MONO;
        }

        ////根據(jù)采樣率奖慌,采樣精度,單雙聲道來得到buffer的大小
        int bufferSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT);

//        AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
        // AudioFormat.ENCODING_PCM_16BIT 設(shè)置音頻數(shù)據(jù)塊是8位還是16位松靡,這里設(shè)置為16位简僧。
       // AudioTrack.MODE_STREAM設(shè)置模式類型,在這里設(shè)置為流類型雕欺,第二種MODE_STATIC
        mAudioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC, // 指定流的類型
                    sampleRateInHz,// 設(shè)置音頻數(shù)據(jù)的采樣率
                    channelConfig,
                   AudioFormat.ENCODING_PCM_16BIT,
                   bufferSize, AudioTrack.MODE_STREAM);

        mAudioTrack.play();//very important  啟動(dòng)音頻設(shè)備
    }

    public synchronized void playTrack(byte[] buffer, int length){
        if(null != mAudioTrack && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING){
            mAudioTrack.write(buffer, 0, length);
        }
    }

    public native void audioToPcm(String inputPath, String outputPath);

    public native void playMusic(String inputPath);

}

【相關(guān)源碼】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末岛马,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子屠列,更是在濱河造成了極大的恐慌啦逆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笛洛,死亡現(xiàn)場(chǎng)離奇詭異蹦浦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撞蜂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門盲镶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝌诡,你說我怎么就攤上這事溉贿。” “怎么了浦旱?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵宇色,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)宣蠕,這世上最難降的妖魔是什么例隆? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮抢蚀,結(jié)果婚禮上镀层,老公的妹妹穿的比我還像新娘。我一直安慰自己皿曲,他們只是感情好唱逢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著屋休,像睡著了一般坞古。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上劫樟,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天痪枫,我揣著相機(jī)與錄音,去河邊找鬼叠艳。 笑死奶陈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的虑绵。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼闽烙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼翅睛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起黑竞,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤捕发,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后很魂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體扎酷,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年遏匆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了法挨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幅聘,死狀恐怖凡纳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情帝蒿,我是刑警寧澤荐糜,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響暴氏,放射性物質(zhì)發(fā)生泄漏延塑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一答渔、第九天 我趴在偏房一處隱蔽的房頂上張望关带。 院中可真熱鬧,春花似錦研儒、人聲如沸豫缨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽好芭。三九已至,卻和暖如春冲呢,著一層夾襖步出監(jiān)牢的瞬間舍败,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工敬拓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邻薯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓乘凸,卻偏偏與公主長(zhǎng)得像厕诡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子营勤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

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