美文网首页
NDK--利用OpenSL ES实现播放FFmpeg解码后的音频

NDK--利用OpenSL ES实现播放FFmpeg解码后的音频

作者: aruba | 来源:发表于2020-07-09 23:12 被阅读0次

    OpenSL ES全称为Open Sound Library for Embedded Systems。OpenSL ES是无授权费、跨平台、针对嵌入式系统 精心优化的硬件音频加速API。当然安卓也使用了它,AudioTrack、MediaPlayer的音频播放,底层都是利用OpenSL。和AudioTrack相同,它只接受流,不支持音频数据的编解码,所以需要结合第三方库来使用。

    为什么要使用OpenSL?一般应用使用安卓SDK提供的API就足够满足使用,但对于专门音频播放的APP,系统提供的方法就有点力不从心了,比如:AudioTrack利用native层调用OpenSL,必不可免的要进行java层流数据转化为native层流数据,这就会造成一定延迟,而对于音频播放APP而言,这是不可接受的。
    前面我们使用了FFmpeg解码音频流,并使用了java层的AudioTrack进行播放,今天来实现在native层直接使用OpenSL播放。
    由于OpenSL系统底层本身就集成,我们只需要在CMakeLists中导入系统动态库就可以了。
    target_link_libraries(
            native-lib
            avcodec-56
            avdevice-56
            avfilter-5
            avformat-56
            avutil-54
            postproc-53
            swresample-1
            swscale-3
    
            android
            //openSL库
            OpenSLES
            ${log-lib})
    
    Opensl的套路如下:

    1、创建引擎接口对象
    2、创建混音器
    3、创建播放器(录音器)
    4、设置缓冲队列和回调函数
    5、设置播放状态
    6、启动回调函数

    这边现在java中编写对应native中的方法
    package com.aruba.ffmpegapplication;
    
    import android.media.AudioFormat;
    import android.media.AudioManager;
    import android.media.AudioTrack;
    import android.os.Bundle;
    import android.os.Environment;
    import android.view.View;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import java.io.File;
    
    public class PcmPlayActivity extends AppCompatActivity {
        static {
            System.loadLibrary("native-lib");
        }
    
        private AudioTrack audioTrack;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_pcm_play);
        }
    
        /**
         * 给native层回调
         *
         * @param sampleRateInHz
         * @param channelCount
         */
        private void create(int sampleRateInHz, int channelCount) {
            int channelConfig = AudioFormat.CHANNEL_OUT_MONO; //单声道
            if (channelCount == 2) {
                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
            }
    
            int buffSize = AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT);//计算最小缓冲区
    
    //    @Deprecated
    //    public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException {
    //            throw new RuntimeException("Stub!");
    //        }
            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, channelConfig, AudioFormat.ENCODING_PCM_16BIT, buffSize, AudioTrack.MODE_STREAM);
            audioTrack.play();
        }
    
        /**
         * 给native层回调
         */
        private void play(byte[] bytes, int size) {
            if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING)
                audioTrack.write(bytes, 0, size);
        }
    
        public void click(View view) {
            switch (view.getId()) {
                case R.id.btn_audiotrack:
                    final File input1 = new File(Environment.getExternalStorageDirectory(), "input.mp3");
    
                    new Thread() {
                        @Override
                        public void run() {
                            playByAudio(input1.getAbsolutePath());
                        }
                    }.start();
                    break;
                case R.id.btn_opensl:
                    final File input2 = new File(Environment.getExternalStorageDirectory(), "input.mp3");
    
                    new Thread() {
                        @Override
                        public void run() {
                            playByOpenSL(input2.getAbsolutePath());
                        }
                    }.start();
                    break;
                case R.id.btn_opensl_stop:
                    stopByOpenSL();
                    break;
            }
    
        }
    
        private native void playByAudio(String inputFilePath);
    
        private native void playByOpenSL(String inputFilePath);
    
        private native void stopByOpenSL();
    }
    
    
    在native层将opensl封装成一个类

    _opensl_helper.h

    //
    // Created by aruba on 2020/7/6.
    //
    
    #ifndef FFMPEGAPPLICATION_OPENSL_HELPER_H
    #define FFMPEGAPPLICATION_OPENSL_HELPER_H
    
    #include <SLES/OpenSLES.h>
    #include <SLES/OpenSLES_Android.h>
    
    class OpenslHelper {
    public:
        //返回结果
        SLresult result;
        //opensl引擎
        SLObjectItf engine;
        //引擎接口
        SLEngineItf engineInterface;
        //混音器
        SLObjectItf mix;
        //环境混响混音器接口
        SLEnvironmentalReverbItf environmentalReverbItf;
        //环境混响混音器环境
        const SLEnvironmentalReverbSettings settings = SL_I3DL2_ENVIRONMENT_PRESET_DEFAULT;
        //播放器
        SLObjectItf player;
        //播放接口
        SLPlayItf playInterface;
        //缓冲区
        SLAndroidSimpleBufferQueueItf bufferQueueItf;
        //音量
        SLVolumeItf volumeItf;
        //播放状态 SL_PLAYSTATE_XXX
        SLuint32 playState;
    
        /**
         * 创建opensl引擎接口
         * @return SLresult
         */
        SLresult createEngine();
    
        /**
         * 是否成功
         * @param result 
         * @return 
         */
        bool isSuccess(SLresult &result);
    
        /**
         * 创建混音器
         * @return SLresult
         */
        SLresult createMix();
    
        /**
         * 创建播放器
         * @param numChannels   声道数
         * @param samplesRate   采样率 SL_SAMPLINGRATE_XX
         * @param bitsPerSample 采样位数(量化格式) SL_PCMSAMPLEFORMAT_FIXED_XX
         * @param channelMask   立体声掩码 SL_SPEAKER_XX | SL_SPEAKER_XX
         * @return SLresult
         */
        SLresult createPlayer(int numChannels, long samplesRate, int bitsPerSample, int channelMask);
    
        //播放
        SLresult play();
    
        //暂停
        SLresult pause();
    
        //停止
        SLresult stop();
    
        /**
        * 播放器会不断调用此函数,我们需要在此回调中不断给缓冲区填充数据
        * @param bufferQueueItf 
        * @param pContext 
        */
        SLresult
        registerCallback(slAndroidSimpleBufferQueueCallback callback);
    
        ~OpenslHelper();
    };
    
    
    #endif //FFMPEGAPPLICATION_OPENSL_HELPER_H
    
    

    _opensl_helper.cpp

    //
    // Created by aruba on 2020/7/6.
    //
    
    #include "_opensl_helper.h"
    
    /**
     * 是否成功
     * @param result 
     * @return 
     */
    bool OpenslHelper::isSuccess(SLresult &result) {
        if (result == SL_RESULT_SUCCESS) {
            return true;
        }
    
        return false;
    }
    
    /**
     * 创建opensl引擎接口
     * @return SLresult
     */
    SLresult OpenslHelper::createEngine() {
        //创建引擎
        result = slCreateEngine(&engine, 0, NULL, 0, NULL, NULL);
        if (!isSuccess(result)) {
            return result;
        }
    
        //实例化引擎,第二个参数为:是否异步
        result = (*engine)->Realize(engine, SL_BOOLEAN_FALSE);
        if (!isSuccess(result)) {
            return result;
        }
    
        //获取引擎接口
        result = (*engine)->GetInterface(engine, SL_IID_ENGINE, &engineInterface);
        if (!isSuccess(result)) {
            return result;
        }
    
        return result;
    }
    
    /**
     * 创建混音器
     * @return SLresult
     */
    SLresult OpenslHelper::createMix() {
        //获取混音器
        result = (*engineInterface)->CreateOutputMix(engineInterface, &mix, 0,
                                                     0, 0);
        if (!isSuccess(result)) {
            return result;
        }
    
        //实例化混音器
        result = (*mix)->Realize(mix, SL_BOOLEAN_FALSE);
        if (!isSuccess(result)) {
            return result;
        }
    
        //获取环境混响混音器接口
        SLresult environmentalResult = (*mix)->GetInterface(mix, SL_IID_ENVIRONMENTALREVERB,
                                                            &environmentalReverbItf);
        if (isSuccess(environmentalResult)) {
            //给混音器设置环境
            (*environmentalReverbItf)->SetEnvironmentalReverbProperties(environmentalReverbItf,
                                                                        &settings);
        }
    
        return result;
    }
    
    /**
     * 创建播放器
     * @param numChannels   声道数
     * @param samplesRate   采样率 SL_SAMPLINGRATE_XX
     * @param bitsPerSample 采样位数(量化格式) SL_PCMSAMPLEFORMAT_FIXED_XX
     * @param channelMask   立体声掩码 SL_SPEAKER_XX | SL_SPEAKER_XX
     * @return SLresult
     */
    SLresult OpenslHelper::createPlayer(int numChannels, long samplesRate, int bitsPerSample,
                                        int channelMask) {
        //1.关联音频流缓冲区  设为2是防止延迟 可以在播放另一个缓冲区时填充新数据
        SLDataLocator_AndroidSimpleBufferQueue buffQueque = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                             2};
    
        //缓冲区格式
    //    typedef struct SLDataFormat_PCM_ {
    //        SLuint32      formatType;    //格式pcm
    //        SLuint32      numChannels;   //声道数
    //        SLuint32      samplesPerSec; //采样率
    //        SLuint32      bitsPerSample; //采样位数(量化格式)
    //        SLuint32      containerSize; //包含位数
    //        SLuint32      channelMask;   //立体声
    //        SLuint32      endianness;    //结束标志位
    //    } SLDataFormat_PCM;
        SLDataFormat_PCM dataFormat_pcm = {SL_DATAFORMAT_PCM, numChannels, samplesRate, bitsPerSample,
                                           bitsPerSample, channelMask,
                                           SL_BYTEORDER_LITTLEENDIAN};
    
        //存放缓冲区地址和格式地址的结构体
    //    typedef struct SLDataSource_ {
    //        void *pLocator;//缓冲区
    //        void *pFormat;//格式
    //    } SLDataSource;
        SLDataSource audioSrc = {&buffQueque, &dataFormat_pcm};
    
        //2.关联混音器
    //    typedef struct SLDataLocator_OutputMix {
    //        SLuint32      locatorType;
    //        SLObjectItf       outputMix;
    //    } SLDataLocator_OutputMix;
        SLDataLocator_OutputMix dataLocator_outputMix = {SL_DATALOCATOR_OUTPUTMIX, mix};
    
        //混音器快捷方式 
    //    typedef struct SLDataSink_ {
    //        void *pLocator;
    //        void *pFormat;
    //    } SLDataSink;
        SLDataSink audioSnk = {&dataLocator_outputMix, NULL};
    
        //3.通过引擎接口创建播放器
    //    SLresult (*CreateAudioPlayer) (
    //            SLEngineItf self,
    //            SLObjectItf * pPlayer,
    //            SLDataSource *pAudioSrc,
    //            SLDataSink *pAudioSnk,
    //            SLuint32 numInterfaces,
    //            const SLInterfaceID * pInterfaceIds,
    //            const SLboolean * pInterfaceRequired
    //    );
        SLInterfaceID ids[3] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME};
        SLboolean required[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
        result = (*engineInterface)->CreateAudioPlayer(engineInterface, &player, &audioSrc, &audioSnk,
                                                       3, ids,
                                                       required);
        if (!isSuccess(result)) {
            return result;
        }
    
        //播放器实例化
        result = (*player)->Realize(player, SL_BOOLEAN_FALSE);
        if (!isSuccess(result)) {
            return result;
        }
    
        //获取播放接口
        result = (*player)->GetInterface(player, SL_IID_PLAY, &playInterface);
        if (!isSuccess(result)) {
            return result;
        }
    
        result = (*player)->GetInterface(player, SL_IID_VOLUME, &volumeItf);
        if (!isSuccess(result)) {
            return result;
        }
    
        //注册缓冲区
        result = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE, &bufferQueueItf);
        if (!isSuccess(result)) {
            return result;
        }
    
        return result;
    }
    
    /**
     * 设置回调接口
     * @return SLresult
     */
    SLresult OpenslHelper::registerCallback(slAndroidSimpleBufferQueueCallback callback) {
        //设置回调接口
        result = (*bufferQueueItf)->RegisterCallback(bufferQueueItf, callback, NULL);
        return result;
    }
    
    //播放
    SLresult OpenslHelper::play() {
        playState = SL_PLAYSTATE_PLAYING;
        result = (*playInterface)->SetPlayState(playInterface, SL_PLAYSTATE_PLAYING);
    
        return result;
    }
    
    //暂停
    SLresult OpenslHelper::pause() {
        playState = SL_PLAYSTATE_PAUSED;
        result = (*playInterface)->SetPlayState(playInterface, SL_PLAYSTATE_PAUSED);
    
        return result;
    }
    
    //停止
    SLresult OpenslHelper::stop() {
        playState = SL_PLAYSTATE_STOPPED;
        result = (*playInterface)->SetPlayState(playInterface, SL_PLAYSTATE_STOPPED);
    
        return result;
    }
    
    //析构
    OpenslHelper::~OpenslHelper() {
        //播放器
        if (player != NULL) {
            (*player)->Destroy(player);
            player = NULL;
            //播放接口
            playInterface = NULL;
            //缓冲区
            bufferQueueItf = NULL;
            //音量
            volumeItf = NULL;
        }
    
        //混音器
        if (mix != NULL) {
            (*mix)->Destroy(mix);
            mix = NULL;
            //环境混响混音器接口
            environmentalReverbItf = NULL;
        }
    
        //opensl引擎
        if (engine != NULL) {
            (*engine)->Destroy(engine);
            engine = NULL;
            //引擎接口
            engineInterface = NULL;
        }
    
        result = NULL;
    }
    
    
    运用我们之前的ffmpeg代码,调用opensl进行播放
    #include <jni.h>
    #include <string>
    #include <android/log.h>
    #include <android/native_window_jni.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdio.h>
    
    extern "C" {
    //编码
    #include "libavcodec/avcodec.h"
    //封装格式处理
    #include "libavformat/avformat.h"
    #include "libswresample/swresample.h"
    //像素处理
    #include "libswscale/swscale.h"
    #include "_opensl_helper.h"
    }
    
    OpenslHelper helper;
    uint8_t *out;
    int buff_size;
    //char *filePath;
    AVFormatContext *formatContext;
    AVCodecContext *codecContext;
    int audio_stream_idx;
    AVPacket *pkt;
    AVFrame *picture;
    SwrContext *swrContext;
    int channel_count;
    int out_size;
    
    void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *pContext);
    
    /**
     * 音频解码PCM,OpenSL播放
     */
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_aruba_ffmpegapplication_PcmPlayActivity_playByOpenSL(JNIEnv *env, jobject instance,
                                                                  jstring inputFilePath_) {
        //初始化opensl
        SLresult result = helper.createEngine();
        if (!helper.isSuccess(result)) {
            LOGE("createEngine失败");
            return;
        }
        result = helper.createMix();
        if (!helper.isSuccess(result)) {
            LOGE("createMix失败");
            return;
        }
    
        const char *inputFilePath = env->GetStringUTFChars(inputFilePath_, 0);
    //    const int size = sizeof(inputFilePath);
    //    filePath = static_cast<char *>(malloc(size));
    //    memcpy(filePath, inputFilePath, size);
    
        //注册FFmpeg中各大组件
        av_register_all();
    
        //打开文件
        formatContext = avformat_alloc_context();
        if (avformat_open_input(&formatContext, inputFilePath, NULL, NULL) != 0) {
            LOGE("打开失败");
            avformat_free_context(formatContext);
            env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
            return;
        }
    
        //将文件信息填充进AVFormatContext
        if (avformat_find_stream_info(formatContext, NULL) < 0) {
            LOGE("获取文件信息失败");
            avformat_free_context(formatContext);
            env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
            return;
        }
    
        //获取视频流的编解码器上下文
        codecContext = NULL;
        audio_stream_idx = -1;
        for (int i = 0; i < formatContext->nb_streams; ++i) {
            if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {//如果是音频流
                codecContext = formatContext->streams[i]->codec;
                audio_stream_idx = i;
                break;
            }
        }
    
        if (codecContext == NULL) {
            avformat_free_context(formatContext);
            env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
            return;
        }
    
        //根据编解码器上下文的id获取视频流解码器
        AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
        //打开解码器
        if (avcodec_open2(codecContext, codec, NULL) < 0) {
            LOGE("解码失败");
            avformat_free_context(formatContext);
            env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
            return;
        }
    
        //开始读每一帧
        //存放压缩数据
        pkt = (AVPacket *) (av_malloc(sizeof(AVPacket)));
        av_init_packet(pkt);
    
        //存放解压数据
        picture = av_frame_alloc();
    
        //音频转码组件上下文
        swrContext = swr_alloc();
        //AV_CH_LAYOUT_STEREO:双声道  AV_SAMPLE_FMT_S16:量化格式 16位 codecContext->sample_rate:采样率 Hz
        swr_alloc_set_opts(swrContext, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16,
                           codecContext->sample_rate,//输出采样率和输入采样率应相同
                           codecContext->channel_layout, codecContext->sample_fmt,
                           codecContext->sample_rate, 0, NULL
        );
        swr_init(swrContext);
    
        //原音频通道数
        channel_count = av_get_channel_layout_nb_channels(codecContext->channel_layout);
        //单通道最大存放转码数据 所占字节 = 采样率*量化格式 / 8
        out_size = 44100 * 16 / 8;
        out = (uint8_t *) (av_malloc(out_size));
    
        //开始播放
        result = helper.createPlayer(2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16,
                                     SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT);
        if (!helper.isSuccess(result)) {
            LOGE("createPlayer失败");
            //释放资源
            av_free(out);
            out = NULL;
            swr_free(&swrContext);
            av_frame_free(&picture);
            avcodec_close(codecContext);
            avformat_free_context(formatContext);
            env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
    
            return;
        }
    
        helper.registerCallback(playerCallback);
        helper.play();
        playerCallback(helper.bufferQueueItf, NULL);
    
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
    }
    
    void release() {
        //释放资源
        av_free_packet(pkt);
        av_freep(out);
        out = NULL;
        swr_free(&swrContext);
        av_frame_free(&picture);
        avcodec_close(codecContext);
        avformat_free_context(formatContext);
    }
    
    void getData(uint8_t **out, int *buff_size) {
        if (out == NULL || buff_size == NULL) {
            return;
        }
        int picture_ptr = 0;
    
        while (av_read_frame(formatContext, pkt) == 0) {//读到每一帧的压缩数据存放在AVPacket
            if (pkt->stream_index == audio_stream_idx) {
                //解码
                avcodec_decode_audio4(codecContext, picture, &picture_ptr, pkt);
    
                LOGE("picture_ptr %d", picture_ptr);
                if (picture_ptr > 0) {
                    //转码
                    swr_convert(swrContext, out, out_size,
                                (const uint8_t **) (picture->data), picture->nb_samples);
    
                    //缓冲区真实大小
                    *buff_size = av_samples_get_buffer_size(NULL, channel_count, picture->nb_samples,
                                                            AV_SAMPLE_FMT_S16, 1);
                    break;
                }
            }
        }
    
        av_free_packet(pkt);
    }
    
    /**
       * 播放器会不断调用此函数,我们需要在此回调中不断给缓冲区填充数据
       * @param bufferQueueItf 
       * @param pContext 
       */
    void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *pContext) {
        if (helper.playState == SL_PLAYSTATE_PLAYING) {
            getData(&out, &buff_size);
            if (out != NULL && buff_size != 0) {
                (*bq)->Enqueue(bq, out, (SLuint32) (buff_size));
            }
        } else if(helper.playState == SL_PLAYSTATE_STOPPED){
            release();
            helper.~SLresult();
            helper = NULL;
        }
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_aruba_ffmpegapplication_PcmPlayActivity_stopByOpenSL(JNIEnv *env, jobject instance) {
        helper.stop();
    }
    
    最后不要忘了在cmake中添加刚刚的_opensl_helper类文件
    经测试后,可以播放音频流了
    项目地址:https://gitee.com/aruba/FFmpegApplication.git

    相关文章

      网友评论

          本文标题:NDK--利用OpenSL ES实现播放FFmpeg解码后的音频

          本文链接:https://www.haomeiwen.com/subject/jncwqktx.html