Android使用FFmpeg(四)--ffmpeg實現音頻播放(使用AudioTrack進行播放)

關於

Android使用FFmpeg(一)--編譯ffmpeg
Android使用FFmpeg(二)--Android Studio配置ffmpeg
Android使用FFmpeg(三)--ffmpeg實現視頻播放
Android使用FFmpeg(四)--ffmpeg實現音頻播放(使用AudioTrack進行播放)
Android使用FFmpeg(五)--ffmpeg實現音頻播放(使用openSL ES進行播放)
Android使用FFmpeg(六)--ffmpeg實現音視頻同步播放
Android使用FFmpeg(七)--ffmpeg實現暫停、快退快進播放java

準備工做

jni調用Java方法
AudioTrack使用
關於音頻git

正文

和視頻播放同樣咱們依照流程圖來實現,從這個流程圖能夠看出,音頻播放和視頻播放的大概流程並無多大差異,只是細節之處須要處理。github

依照流程來實現

1.註冊組件,打開音頻文件並獲取內容,找到音頻流:緩存

av_register_all();
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    //open
    if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {
        LOGE("%s","打開輸入視頻文件失敗");
        return;
    }
    //獲取視頻信息
    if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
        LOGE("%s","獲取視頻信息失敗");
        return;
    }
    int audio_stream_idx=-1;
    int i=0;
    for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            LOGE("  找到音頻id %d", pFormatCtx->streams[i]->codec->codec_type);
            audio_stream_idx=i;
            break;
        }
    }

2.獲取解碼的裝置,申請avframe和avpacket:佈局

//獲取解碼的裝置上下文
    AVCodecContext *pCodecCtx=pFormatCtx->streams[audio_stream_idx]->codec;
    //獲取解碼的裝置
    AVCodec *pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
    //打開解碼的裝置
    if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
    }
    //申請avpakcet,裝解碼前的數據
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //申請avframe,裝解碼後的數據
    AVFrame *frame = av_frame_alloc();

3.初始化SwrContext,進行重採樣post

//獲得SwrContext ,進行重採樣,具體參考http://blog.csdn.net/jammg/article/details/52688506
    SwrContext *swrContext = swr_alloc();
    //緩存區
    uint8_t *out_buffer = (uint8_t *) av_malloc(44100 * 2);
//輸出的聲道佈局(立體聲)
    uint64_t  out_ch_layout=AV_CH_LAYOUT_STEREO;
//輸出採樣位數  16位
    enum AVSampleFormat out_formart=AV_SAMPLE_FMT_S16;
//輸出的採樣率必須與輸入相同
    int out_sample_rate = pCodecCtx->sample_rate;

//swr_alloc_set_opts將PCM源文件的採樣格式轉換爲本身但願的採樣格式
    swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
                       pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0,
                       NULL);
    swr_init(swrContext);

4.經過while循環讀取內容,並經過AudioTrack進行播放:ui

//    獲取通道數  2
    int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
//    反射獲得Class類型
    jclass david_player = env->GetObjectClass(instance);
//    反射獲得createAudio方法
    jmethodID createAudio = env->GetMethodID(david_player, "createTrack", "(II)V");
//    反射調用createAudio
    env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);
    jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");
int got_frame;
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == audio_stream_idx) {
//            解碼  mp3   編碼格式frame----pcm   frame
            avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
            if (got_frame) {
                LOGE("解碼");
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
//                緩衝區的大小
                int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
                                                      AV_SAMPLE_FMT_S16, 1);
                jbyteArray audio_sample_array = env->NewByteArray(size);
                env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
                env->CallVoidMethod(instance, audio_write, audio_sample_array, size);
                env->DeleteLocalRef(audio_sample_array);
            }
        }
    }

5.釋放須要釋放的資源:編碼

av_frame_free(&frame);
    swr_free(&swrContext);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
    env->ReleaseStringUTFChars(input_, input);

java層建立AudioTrack方法。

jni經過調用java層的audiotrack方法來實現播放,可是java層的audiotrack方法又是經過調用底層的openesl es來進行播放,至關於繞了一個圈,因此在下篇文章中將實現使用opensl es來直接播放音頻。spa

public class MusicPlay {
    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");
    }
    public native void playSound(String input);

    private AudioTrack audioTrack;
    //    這個方法  是C進行調用 
    public void createTrack(int sampleRateInHz,int nb_channals) {
        int channaleConfig;//通道數
        if (nb_channals == 1) {
            channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
        } else if (nb_channals == 2) {
            channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
        }else {
            channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
        }
        int buffersize=AudioTrack.getMinBufferSize(sampleRateInHz,
                channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
                AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
        audioTrack.play();
    }
    //C傳入音頻數據
    public void playTrack(byte[] buffer, int lenth) {
        if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
            audioTrack.write(buffer, 0, lenth);
        }
    }
}

小結

按照流程,使用AudioTrack播放音頻就是這麼回事。經過和上篇的播放視頻相比較,流程就差很少了:註冊->解封裝->獲取流->解碼->播放。
源碼地址.net

相關文章
相關標籤/搜索