ffmpeg-OpenSLES播放音频数据

前言

ndk调试命令

adb logcat | ndk-stack -sym app/build/intermediates/cmake/debug/obj/armeabi

上两篇文章
FFmpeg - 初探ffmepg并解码数据
FFmpeg-多线程解码播放
下面是基于上面所写的文章系列

OpenSLES播放音频的流程

OpenSLES和OpenGLES都是自带的
xxxes和xxx之间基本上没有区别,主要区别就是xxxes是xxx的精简
播放音频流程

  • 创建引擎接口
  • 创建混音器和设置参数
  • 创建播放器
  • 设置缓存队列和回掉函数
  • 设置播放状态且调用回掉函数

大家可以看google提供的案例

FILE *pcmFile;
void *pcmBuffer;

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {

    if (!feof(pcmFile)) {
        fread(pcmBuffer, 1, 44100 * 2 * 2, pcmFile);
        (*bq)->Enqueue(bq, pcmBuffer, 44100 * 2 * 2);
    } else {
        fclose(pcmFile);
        free(pcmBuffer);
    }

}

void initCreateOpenSLES() {

    //1、创建引擎接口
    SLObjectItf pEngineObject;
    SLEngineItf engineEngine;
    slCreateEngine(&pEngineObject, 0, NULL, 0, NULL, NULL);
    //实现
    (*pEngineObject)->Realize(pEngineObject, SL_BOOLEAN_FALSE);
    //获取引擎接口
    (*pEngineObject)->GetInterface(pEngineObject, SL_IID_ENGINE, &engineEngine);
    //2、创建混音器和设置参数
    SLObjectItf outputMixObject;
    SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;
    const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    const SLEnvironmentalReverbSettings reverbSettings =
            SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
    //创建
    (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
    //实现
    (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    //获取接口
    (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                     &outputMixEnvironmentalReverb);
    //设置参数
    (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(
            outputMixEnvironmentalReverb, &reverbSettings);
    //3、创建播放器
    SLObjectItf bqPlayerObject = NULL;
    SLPlayItf bqPlayerPlay;
    //指定可以播放什么
    SLDataLocator_AndroidSimpleBufferQueue simpleBufferQueue = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM formatPcm = {SL_DATAFORMAT_PCM,
                                  2,
                                  SL_SAMPLINGRATE_44_1,
                                  SL_PCMSAMPLEFORMAT_FIXED_16,
                                  SL_PCMSAMPLEFORMAT_FIXED_16,
                                  SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
                                  SL_BYTEORDER_LITTLEENDIAN//结尾
    };
    SLDataSource pAudioSrc = {&simpleBufferQueue, &formatPcm};

    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    const SLInterfaceID pInterfaceIds[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME,
                                            SL_IID_PLAYBACKRATE,//声音变速
            /*SL_IID_MUTESOLO,*/};
    const SLboolean pInterfaceRequired[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
            /*SL_BOOLEAN_TRUE,*/ };
    (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &pAudioSrc, &audioSnk, 3,
                                       pInterfaceIds, pInterfaceRequired);
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
    //4、设置缓存队列和回掉函数
    SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                    &bqPlayerBufferQueue);
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL);
    //5、设置播放状态
    (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);

    //6、调用回掉函数
    bqPlayerCallback(bqPlayerBufferQueue, NULL);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_peakmain_ndkproject_MainActivity_playPCM(JNIEnv *env, jobject instance, jstring url_) {
    const char *url = env->GetStringUTFChars(url_, 0);
    pcmFile = fopen(url, "r");
    pcmBuffer = malloc(44100 * 2 * 2);

    initCreateOpenSLES();
    env->ReleaseStringUTFChars(url_, url);
}

加入OpenSLES和重整项目代码

  • 首先CmakeLists我们需要添加OpenSLES的依赖
target_link_libraries( # Specifies the target library.
# 链接额外的 ffmpeg 的编译
        music_play
        opencv_java
        # 编解码(最重要的库)
        avcodec-57
        # 设备信息
        avdevice-57
        # 滤镜特效处理库
        avfilter-6
        # 封装格式处理库
        avformat-57
        # 工具库(大部分库都需要这个库的支持)
        avutil-55
        # 后期处理
        postproc-54
        # 音频采样数据格式转换库
        swresample-2
        # 视频像素数据格式转换
        swscale-4
        # 链接 android ndk 自带的一些库
        android
        jnigraphics
          OpenSLES
        # Links the target library to the log library
        # included in the NDK.
        log)
  • music_play的修改
//
// Created by admin on 2019/10/24.
//
#include 


#include "PeakmainJNICall.h"
#include "PeakmainFFmpeg.h"


PeakmainJNICall *pJNICall;
PeakmainFFmpeg *ffmpeg;

JavaVM *pJavaVM = NULL;
//重写so加载时的一个方法
extern "C"
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *javaVM, void *resverved) {
    pJavaVM = javaVM;
    JNIEnv *env;
    if (javaVM->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return -1;
    }
    return JNI_VERSION_1_4;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_peakmain_mall_ndk_media_MusicPlayer_npay(JNIEnv *env, jobject instance) {
    if (ffmpeg != NULL) {
         ffmpeg->play();
    }
}
extern "C"
JNIEXPORT void JNICALL
Java_com_peakmain_mall_ndk_media_MusicPlayer_nPrepare(JNIEnv *env, jobject instance, jstring url_) {

    const char *url = env->GetStringUTFChars(url_, 0);
    if (ffmpeg == NULL) {
        pJNICall = new PeakmainJNICall(pJavaVM, env, instance);

        ffmpeg = new PeakmainFFmpeg(pJNICall, url);

        ffmpeg->prepared();
    }


    env->ReleaseStringUTFChars(url_, url);

}
  • PeakmainJNICall.h和PeakmainJNICall.cpp
//
// Created by admin on 2019/10/28.
//

#ifndef NDK_PEAKMAINJNICALL_H
#define NDK_PEAKMAINJNICALL_H


#include 
enum ThreadMode{
    THREAD_CHILD,THREAD_MAIN
};
class PeakmainJNICall {
public:
    JavaVM *javaVM;
    JNIEnv *jniEnv;
    jmethodID jPlayerErrorMid;
    jobject jobject;
public:
    PeakmainJNICall(JavaVM *javaVM, JNIEnv *jniEnv, _jobject * jobject);
    ~PeakmainJNICall();



public:
    void callPlayerJniError(ThreadMode  threadMode,int code, char *msg);
};


#endif //NDK_PEAKMAINJNICALL_H

//
// Created by admin on 2019/10/28.
//

#include "PeakmainJNICall.h"
#include "ConstantsDefine.h"

PeakmainJNICall::PeakmainJNICall(JavaVM *javaVM, JNIEnv *jniEnv, _jobject *jobject) {
    this->javaVM = javaVM;
    this->jniEnv = jniEnv;
    this->jobject = jniEnv->NewGlobalRef(jobject);

    jclass jPlayerClass = jniEnv->GetObjectClass(jobject);
    jPlayerErrorMid = jniEnv->GetMethodID(jPlayerClass, "onError", "(ILjava/lang/String;)V");
}


PeakmainJNICall::~PeakmainJNICall() {
    jniEnv->DeleteGlobalRef(jobject);
}

void PeakmainJNICall::callPlayerJniError(ThreadMode threadMode, int code, char *msg) {
    LOGE("报错进来了");
    if (threadMode == THREAD_MAIN) {
        jstring jsg = jniEnv->NewStringUTF(msg);
        jniEnv->CallVoidMethod(jobject, jPlayerErrorMid, code, jsg);
        jniEnv->DeleteLocalRef(jsg);
    } else if (threadMode == THREAD_CHILD) {
        //获取当前线程的jniEnv
        JNIEnv *env;
        if (javaVM->AttachCurrentThread(&env, 0) != JNI_OK) {
            LOGE("get child thread jni error");
            return;
        }
        jstring jsg = env->NewStringUTF(msg);
        env->CallVoidMethod(jobject, jPlayerErrorMid, code, jsg);
        env->DeleteLocalRef(jsg);
        javaVM->DetachCurrentThread();
    }

}
  • PeakmainFFmpeg.h和PeakmainFFmpeg.cpp
//
// Created by admin on 2019/10/28.
//

#ifndef NDK_PEAKMAINFFMPEG_H
#define NDK_PEAKMAINFFMPEG_H

#include "PeakmainJNICall.h"
#include "PeakmainAudio.h"

extern "C" {
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"

}

#include 


class PeakmainFFmpeg {
public:
    //上下文
    AVFormatContext *pFormatContext = NULL;
    AVCodecContext *pCodecContext = NULL;
    SwrContext *swrContext = NULL;
    char* url = NULL;
    PeakmainJNICall *pJniCall = NULL;
    PeakmainAudio *pAudio = NULL;
public:
    PeakmainFFmpeg(PeakmainJNICall *jniCall, const char *url);

    ~PeakmainFFmpeg();

public:
    void play();

    void prepared();

    void prepared(ThreadMode threadMode);


    void callPlayerJniError(ThreadMode threadMode, int code, char *msg);

    void release();


    void prepareAsync();
};
#endif //NDK_PEAKMAINFFMPEG_H
//
// Created by admin on 2019/10/28.
//

#include "PeakmainFFmpeg.h"
#include "ConstantsDefine.h"


PeakmainFFmpeg::PeakmainFFmpeg(PeakmainJNICall *jniCall, const char *url) {
    this->pJniCall = jniCall;
    //复制一份url
    this->url = (char *) malloc(strlen(url) + 1);
    memcpy(this->url, url, strlen(url) + 1);
}


PeakmainFFmpeg::~PeakmainFFmpeg() {
    release();
}




void PeakmainFFmpeg::play() {
    if(pAudio!=NULL){
        pAudio->play();
    }


}

void PeakmainFFmpeg::callPlayerJniError(ThreadMode mode, int code, char *msg) {

    //释放资源
    release();
    LOGE("进来了0");
    //回掉
    pJniCall->callPlayerJniError(mode, code, msg);

}


void PeakmainFFmpeg::prepared() {
    prepared(THREAD_MAIN);

}

void *threadPrepare(void *context) {
    PeakmainFFmpeg *pFFmpeg = (PeakmainFFmpeg *) context;
    pFFmpeg->prepared(THREAD_CHILD);
    return 0;
}

void PeakmainFFmpeg::prepareAsync() {
    // 创建一个线程去播放,多线程编解码边播放
    pthread_t prepareThreadT;
    pthread_create(&prepareThreadT, NULL, threadPrepare, this);
    pthread_detach(prepareThreadT);
}


void PeakmainFFmpeg::prepared(ThreadMode threadMode) {
    av_register_all();
    avformat_network_init();
    int formatOpenInputRes = 0;
    int formatFindStreamInfoRes = 0;
    AVCodecParameters *pCodecParameters;
    AVCodec *pCodec = NULL;
    int codecParametersToContextRes = -1;
    int codecOpenRes = -1;

    formatOpenInputRes = avformat_open_input(&pFormatContext, url, NULL, NULL);
    if (formatOpenInputRes != 0) {
        // 第一件事,需要回调给 Java 层(下次课讲)
        // 第二件事,需要释放资源
        LOGE("format open input error: %s", av_err2str(formatOpenInputRes));
        callPlayerJniError(threadMode, formatOpenInputRes, av_err2str(formatOpenInputRes));
        return;
    }

    formatFindStreamInfoRes = avformat_find_stream_info(pFormatContext, NULL);
    if (formatFindStreamInfoRes < 0) {
        LOGE("format find stream info error: %s", av_err2str(formatFindStreamInfoRes));
        // 这种方式一般不推荐这么写,但是的确方便
        callPlayerJniError(threadMode, formatFindStreamInfoRes,
                           av_err2str(formatFindStreamInfoRes));
        return;
    }

    // 查找音频流的 index
    int audioStramIndex = av_find_best_stream(pFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1,
                                              -1,
                                              NULL, 0);
    if (audioStramIndex < 0) {
        LOGE("format audio stream error.");
        callPlayerJniError(threadMode, FIND_STREAM_ERROR_CODE, "format audio stream error");
        return;
    }

    // 查找解码
    pCodecParameters = pFormatContext->streams[audioStramIndex]->codecpar;
    pCodec = avcodec_find_decoder(pCodecParameters->codec_id);
    if (pCodec == NULL) {
        LOGE("codec find audio decoder error");
        // 这种方式一般不推荐这么写,但是的确方便
        callPlayerJniError(threadMode, CODEC_FIND_DECODER_ERROR_CODE,
                           "codec find audio decoder error");
        return;
    }
    // 打开解码器
    pCodecContext = avcodec_alloc_context3(pCodec);
    if (pCodecContext == NULL) {
        LOGE("codec alloc context error");
        // 这种方式一般不推荐这么写,但是的确方便
        callPlayerJniError(threadMode, CODEC_ALLOC_CONTEXT_ERROR_CODE, "codec alloc context error");
        return;
    }
    codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
    if (codecParametersToContextRes < 0) {
        LOGE("codec parameters to context error: %s", av_err2str(codecParametersToContextRes));
        callPlayerJniError(threadMode, codecParametersToContextRes,
                           av_err2str(codecParametersToContextRes));
        return;
    }

    codecOpenRes = avcodec_open2(pCodecContext, pCodec, NULL);
    if (codecOpenRes != 0) {
        LOGE("codec audio open error: %s", av_err2str(codecOpenRes));
        callPlayerJniError(threadMode, codecOpenRes, av_err2str(codecOpenRes));
        return;
    }

    // ---------- 重采样 start ----------
    int64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    enum AVSampleFormat out_sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_S16;
    int out_sample_rate = AUDIO_SAMPLE_RATE;
    int64_t in_ch_layout = pCodecContext->channel_layout;
    enum AVSampleFormat in_sample_fmt = pCodecContext->sample_fmt;
    int in_sample_rate = pCodecContext->sample_rate;
    swrContext = swr_alloc_set_opts(NULL, out_ch_layout, out_sample_fmt,
                                    out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
    if (swrContext == NULL) {
        // 提示错误
        callPlayerJniError(threadMode, SWR_ALLOC_SET_OPTS_ERROR_CODE, "swr alloc set opts error");
        return;
    }
    int swrInitRes = swr_init(swrContext);
    if (swrInitRes < 0) {
        callPlayerJniError(threadMode, SWR_CONTEXT_INIT_ERROR_CODE, "swr context swr init error");
        return;
    }
    pAudio = new PeakmainAudio(audioStramIndex, pJniCall, pCodecContext, pFormatContext,swrContext);
    // ---------- 重采样 end ----------
    // 回调到 Java 告诉他准备好了------



}

void PeakmainFFmpeg::release() {

    if (pCodecContext != NULL) {
        avcodec_close(pCodecContext);
        avcodec_free_context(&pCodecContext);
        pCodecContext = NULL;
    }

    if (pFormatContext != NULL) {
        avformat_close_input(&pFormatContext);
        avformat_free_context(pFormatContext);
        pFormatContext = NULL;
    }

    if (swrContext != NULL) {
        swr_free(&swrContext);
        free(swrContext);
        swrContext = NULL;
    }

    avformat_network_deinit();

    if (url != NULL) {
        free(url);
        url = NULL;
    }
}
  • PeakmainAudio.h和PeakmainAudio.cpp主要用来播放
//
// Created by admin on 2019/10/29.
//

#ifndef NDK_PEAKMAINAUDIO_H
#define NDK_PEAKMAINAUDIO_H

#include "PeakmainJNICall.h"
#include "utils.h"
#include 
#include 
extern "C" {
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"

}

class PeakmainAudio {
public:
    //上下文
    AVFormatContext *pFormatContext;
    AVCodecContext *pCodecContext;
    struct SwrContext *swrContext;
    uint8_t *resampleOutBuffer;
    char *url;
    int audioStreamIndex;
    PeakmainJNICall *pJNICall;


public:
    PeakmainAudio( int audioStreamIndex,PeakmainJNICall *pJNICall
            ,AVCodecContext *pCodecContext,AVFormatContext *pFormatContext,SwrContext *swrContext);

    void play();
    void initCreateOpenSLES();

    int resampleAudio();
};


#endif //NDK_PEAKMAINAUDIO_H
//
// Created by admin on 2019/10/29.
//

#include "PeakmainAudio.h"


PeakmainAudio::PeakmainAudio(int audioStreamIndex, PeakmainJNICall *pJNICall,
                             AVCodecContext *pCodecContext, AVFormatContext *pFormatContext,
                             SwrContext *swrContex) {
    this->audioStreamIndex = audioStreamIndex;
    this->pJNICall = pJNICall;
    this->pCodecContext = pCodecContext;
    this->pFormatContext = pFormatContext;
    resampleOutBuffer = (uint8_t *) malloc(pCodecContext->frame_size * 2 * 2);
    this->swrContext = swrContex;
}

void *threadPlay(void *context) {
    PeakmainAudio *pFFmepg = (PeakmainAudio *) context;
    pFFmepg->initCreateOpenSLES();

    return 0;
}

void PeakmainAudio::play() {
    //多线程播放边解码边播放
    pthread_t playThread;
    pthread_create(&playThread, NULL, threadPlay, this);
    pthread_detach(playThread);
}

int PeakmainAudio::resampleAudio() {
    int dataSize = 0;
    AVPacket *pPacket = av_packet_alloc();
    AVFrame *pFrame = av_frame_alloc();

    while (av_read_frame(pFormatContext, pPacket) >= 0) {
        if (pPacket->stream_index == audioStreamIndex) {
            // Packet 包,压缩的数据,解码成 pcm 数据
            int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
            if (codecSendPacketRes == 0) {
                int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
                if (codecReceiveFrameRes == 0) {
                    // AVPacket -> AVFrame
                    LOGE("解码音频帧");

                    // 调用重采样的方法
                    dataSize = swr_convert(swrContext, &resampleOutBuffer, pFrame->nb_samples,
                                           (const uint8_t **) pFrame->data, pFrame->nb_samples);
                    dataSize = dataSize * 2 * 2;
                    LOGE("解码音频帧");
                    // write 写到缓冲区 pFrame.data -> javabyte
                    // size 是多大,装 pcm 的数据
                    // 1s 44100 点  2通道 ,2字节    44100*2*2
                    // 1帧不是一秒,pFrame->nb_samples点
                    break;
                }
            }
        }
        // 解引用
        av_packet_unref(pPacket);
        av_frame_unref(pFrame);
    }
    // 1. 解引用数据 data , 2. 销毁 pPacket 结构体内存  3. pPacket = NULL
    av_packet_free(&pPacket);
    av_frame_free(&pFrame);
    return dataSize;
}

void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    PeakmainAudio *pFfmpeg = (PeakmainAudio *) (context);
    int dataSize = pFfmpeg->resampleAudio();
    (*bq)->Enqueue(bq, pFfmpeg->resampleOutBuffer, dataSize);
}

void PeakmainAudio::initCreateOpenSLES() {

    /*OpenSLES OpenGLES 都是自带的
      XXXES 与 XXX 之间可以说是基本没有区别,区别就是 XXXES 是 XXX 的精简
      而且他们都有一定规则,命名规则 slXXX() , glXXX3f*/
    // 3.1 创建引擎接口对象
    SLObjectItf engineObject = NULL;
    SLEngineItf engineEngine;
    slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    // realize the engine
    (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    // get the engine interface, which is needed in order to create other objects
    (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
    // 3.2 设置混音器
    static SLObjectItf outputMixObject = NULL;
    const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
    (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;
    (*outputMixObject)->GetInterface(outputMixObject, SL_IID_ENVIRONMENTALREVERB,
                                     &outputMixEnvironmentalReverb);
    SLEnvironmentalReverbSettings reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;
    (*outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(outputMixEnvironmentalReverb,
                                                                      &reverbSettings);
    // 3.3 创建播放器
    SLObjectItf pPlayer = NULL;
    SLPlayItf pPlayItf = NULL;
    SLDataLocator_AndroidSimpleBufferQueue simpleBufferQueue = {
            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
    SLDataFormat_PCM formatPcm = {
            SL_DATAFORMAT_PCM,
            2,
            SL_SAMPLINGRATE_44_1,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_PCMSAMPLEFORMAT_FIXED_16,
            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
            SL_BYTEORDER_LITTLEENDIAN};
    SLDataSource audioSrc = {&simpleBufferQueue, &formatPcm};
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};
    SLInterfaceID interfaceIds[3] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_PLAYBACKRATE};
    SLboolean interfaceRequired[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    (*engineEngine)->CreateAudioPlayer(engineEngine, &pPlayer, &audioSrc, &audioSnk, 3,
                                       interfaceIds, interfaceRequired);
    (*pPlayer)->Realize(pPlayer, SL_BOOLEAN_FALSE);
    (*pPlayer)->GetInterface(pPlayer, SL_IID_PLAY, &pPlayItf);
    // 3.4 设置缓存队列和回调函数
    SLAndroidSimpleBufferQueueItf playerBufferQueue;
    (*pPlayer)->GetInterface(pPlayer, SL_IID_BUFFERQUEUE, &playerBufferQueue);
    (*playerBufferQueue)->RegisterCallback(playerBufferQueue, playerCallback, this);
    // 3.5 设置播放状态
    (*pPlayItf)->SetPlayState(pPlayItf, SL_PLAYSTATE_PLAYING);
    // 3.6 调用回调函数
    playerCallback(playerBufferQueue, this);
}

你可能感兴趣的:(ffmpeg-OpenSLES播放音频数据)