十七、音频转PCM和使用native调用AudioTrack播放音频文件

一、Android AudioTrack简介

在Android中播放音频可以用MediaPlayer和AudioTrack两种方案的,但是两种方案是有很大区别的,MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等。而AudioTrack只能播放PCM数据流。
事实上,两种本质上是没啥区别的,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,最后由AudioFlinger进行混音,传递音频给硬件播放出来。利用AudioTrack播放只是跳过Mediaplayer的解码部分而已。Mediaplayer的解码核心部分是基于OpenCORE 来实现的,支持通用的音视频和图像格式,codec使用的是OpenMAX接口来进行扩展。因此使用audiotrack播放mp3文件的话,要自己加入一个音频解码器,如libmad。否则只能播放PCM数据,如大多数WAV格式的音频文件。
如果是实时的音频数据,那么只能用AudioTrack进行播放。

音频所占用字节数 = 通道数 * 采用频率(Hz) * 采用位数(byte)

二、具体实现

native-lib.cpp

2.1音频转PCM和使用native调用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 音频转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;

    //输出采样位数
    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调用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;

    //输出采样位数
    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);

   //调用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;
        }

        ////根据采样率,采样精度,单双声道来得到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 设置音频数据块是8位还是16位,这里设置为16位。
       // AudioTrack.MODE_STREAM设置模式类型,在这里设置为流类型,第二种MODE_STATIC
        mAudioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC, // 指定流的类型
                    sampleRateInHz,// 设置音频数据的采样率
                    channelConfig,
                   AudioFormat.ENCODING_PCM_16BIT,
                   bufferSize, AudioTrack.MODE_STREAM);

        mAudioTrack.play();//very important  启动音频设备
    }

    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);

}

【相关源码】

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容