4. 使用ffmpeg库解码mp3文件并用opensles来播放音乐

AudioTrackTest-opensles

OpenSL使用流程分析

OpenSL ES的API都是基于对象和接口的方式来提供的。
opensl_player.png

(1)先create Engine的ObjectItf,然后Realize Engine的ObjectItf,然后Engine的ObjectItf通过GetInterface创建EngineItf

(2)EngineItf通过CreateAudioPlayer接口来创建audio player的ObjectItf,通过CreateOutputMix接口来创建outputmix的ObjectItf

(3)audio player的ObjectItf通过GetInterface来创建bufferqueue和play Itf

整体流程:

(1)ffmpeg解析mp3,得到PCM,打包成AudioPacket,放到packet_pool中。
(2)opensl从packet_pool中读取AudioPacket,通过Enqueue函数,压进buffer中

源码分析

1. setAudioDataSource

进行ffmpeg音频解码初始化操作和openSLES初始化操作
JNIEXPORT jboolean JNICALL Java_com_example_audiotracktest_opensles_SoundTrackController_setAudioDataSource
        (JNIEnv *env, jobject obj, jstring acPathParam) {
    const char* acPath = env->GetStringUTFChars(acPathParam, NULL);
    soundService = SoundService::GetInstance();
    // 1. 初始化ffmpeg相关的东西
    soundService->initSongDecoder(acPath);
    // 2. 初始化OpenSLES相关的东西
    SLresult result = soundService->initSoundTrack();
    env->ReleaseStringUTFChars(acPathParam, acPath);
    return true;
}

1. soundService->initSongDecoder

void SoundService::initSongDecoder(const char *acPath) {
    LOGI("enter SoundService::initSongDecoder");
    mDecoderController = new AccompanyDecoderController();
    // 对ffmpeg进行初始化操作
    mDecoderController->init(acPath);
    // 设置buffer count的数量
    this->mBufferNums = 2;
    this->mCurrentFrame = 0;
    // 设置一个包的buffer大小,channel * sample rate * bytes per sample
    mPacketBufferSize = 2 * 44100 * 2;
    mBufferSize = mPacketBufferSize * mBufferNums;
}

2. soundService->initSoundTrack

SLresult SoundService::initSoundTrack() {
    LOGI("enter SoundService::initSoundTrack");
    SLresult result;
    OpenSLESContext* openSLESContext = OpenSLESContext::GetInstance();
    // 1. 创建engine对象
    mEngine = openSLESContext->getEngine();

    // 2. 创建outputmix对象
    result = CreateOutputMix();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }

    // 3. 实例化outputmix对象
    result = RealizeObject(mOutputMixObject);
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }

    // 4. 创建两个buffer,mBuffer和mTarget
    InitPlayerBuffer();
    // 5. 创建AudioPlayer Itf
    result = CreateBufferQueueAudioPlayer();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }

    // 6. 实例化AudioPlayer对象
    result = RealizeObject(mAudioPlayerObject);
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }
    // 7. 获取AudioPlayer对象的接口
    result = GetAudioPlayerBufferQueueInterface();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }
    // 8. 注册player的回调
    result = RegisterPlayerCallback();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }
    // 9. 获取audioplayer的接口
    result = GetAudioPlayerInterface();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }
    LOGI("leave init");
    return SL_RESULT_SUCCESS;
}
OpenSLES的对象初始化的过程:

(1)创建SLObjectItf(create)

(2)实例化SLObjectItf(Realize)

(3)通过实例化后的对象的函数GetInterface来获取接口/其他对象(GetInterface)

1. 初始化engine
1. 创建SLObjectItf mEngineObject对象
    slCreateEngine(&mEngineObject, ARRAY_LEN(engineOptions),engineOptions,0,0,0);
2. 实例化SLObjectItf mEngineObject对象
    (*mEngineObject)->Realize(mEngineObject, SL_BOOLEAN_FALSE)
3. 通过mEngineObject获取SLEngineItf mEngine对象
    (*mEngineObject)->GetInterface(mEngineObject, SL_IID_ENGINE, &mEngine);
2. 初始化outputmix
1. 通过mEngine对象来创建SLObjectItf mOutputMixObject
    (*mEngine)->CreateOutputMix(mEngine, &mOutputMixObject, 0, 0, 0);
2. 实例化SLObjectItf mOutputMixObject对象
3. 初始化audioplayer
1. 通过mEngine对象来创建SLObjectItf mAudioPlayerObject
    SLresult CreateBufferQueueAudioPlayer() {
        SLDataLocator_AndroidSimpleBufferQueue dataSourceLocator = {
                SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, // locator type
                // 注意,这里设置的数字应该和后面play函数中调用的Enqueue函数的次数一致
                // 不然的话,就会出现SL_RESULT_BUFFER_INSUFFICIENT错误。
                1  // buffer count
        };
        SLDataFormat_PCM dataSourceFormat = {
                SL_DATAFORMAT_PCM,  // format type
                2,  // channel count
                SL_SAMPLINGRATE_44_1, // samples per second in millihertz
                SL_PCMSAMPLEFORMAT_FIXED_16, // bits per sample
                SL_PCMSAMPLEFORMAT_FIXED_16, // container size
                SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, // channel mask
                SL_BYTEORDER_LITTLEENDIAN // endianness
        };
        SLDataSource dataSource = {
                &dataSourceLocator,
                &dataSourceFormat
        };
        SLDataLocator_OutputMix dataSinkLocator = {
                SL_DATALOCATOR_OUTPUTMIX,
                mOutputMixObject
        };
        SLDataSink dataSink = {
                &dataSinkLocator,
                0
        };
        SLInterfaceID interfaceIds[] = { SL_IID_BUFFERQUEUE };
        SLboolean requiredInterfaces[] { SL_BOOLEAN_TRUE };

        return (*mEngine)->CreateAudioPlayer(mEngine, &mAudioPlayerObject,
                &dataSource, &dataSink, ARRAY_LEN(interfaceIds),
                interfaceIds, requiredInterfaces);
    };
(1)配置SLDataSource
    <1> 配置SLDataLocator_AndroidSimpleBufferQueue buffer类型和数量
    <2> 配置SLDataFormat_PCM PCM的类型,通道数,采样率,定点,扬声器,小端模式
(2)配置SLDataSink:就是音频输出
(3)需要请求的接口:SLInterfaceID SL_IID_BUFFERQUEUE为缓冲队列,SL_IID_VOLUME为音量;然后对应的requiredInterfaces设为SL_BOOLEAN_TRUE

2. 实例化SLObjectItf mAudioPlayerObject对象
3. 通过mAduioPlayerObject对象来获取SLPlayItf mAudioPlayerPlay对象
4. 通过mAduioPlayerObject对象来获取SLAndroidSimpleBufferQueueItf mAudioPlayerBufferQueue对象
    (*mAudioPlayerObject)->GetInterface(mAudioPlayerObject, SL_IID_BUFFERQUEUE,&mAudioPlayerBufferQueue);

综上看:mAudioPlayerPlay对象和mAudioPlayerBufferQueue对象隶属于mAudioPlayerObject对象
4. 注册player的回调
1. 通过mAudioPlayerBufferQueue对象来注册PlayerCallback回调函数,来向buffer中填充数据
    (*mAudioPlayerBufferQueue)->RegisterCallback(mAudioPlayerBufferQueue, PlayerCallback, this);

2. play开始播放

JNIEXPORT void JNICALL Java_com_example_audiotracktest_opensles_SoundTrackController_play
(JNIEnv * env, jobject obj) {
    if (NULL != soundService) {
        // 1. 开始播放
        soundService->play();
    }
}

1. play开始播放

SLresult SoundService::play() {
    LOGI("enter SoundService::play()...");
    // 1. 设置AudioPlayer的状态为playing
    // (*mAudioPlayerPlay)->SetPlayState(mAudioPlayerPlay, SL_PLAYSTATE_PLAYING);
    SLresult result = SetAudioPlayerStatePlaying();
    if (SL_RESULT_SUCCESS != result) {
        return result;
    }
    mPlayingState = PLAYING_STATE_PLAYING;

        int ret = 0;
        if (NULL != mDecoderController) {
            /*for (int j = 0; j < 33; j++) {
                int pSize = -1;
                pSize = mDecoderController->readSamples(mTarget + ret, mPacketBufferSize/2, NULL);
                if (0 < pSize) {
                    ret += pSize;
                } else {
                    break;
                }
                LOGI("ret = %d", ret);
            } */
            // 2. 从ffmpeg中读取数据
            ret = mDecoderController->readSamples(mTarget + ret, mPacketBufferSize/2, NULL);
        }
        if (0 < ret) {
            convertByteArrayFromShortArray(mTarget, ret, mBuffer);
            // 3. 然后将数据通过AudioPlayerBufferQueue填充到buffer中
            (*mAudioPlayerBufferQueue)->Enqueue(mAudioPlayerBufferQueue, mBuffer, ret * 2);
        }
    LOGI("out SoundService::play()...");
    // 4. 然后play函数进入到死循环中,不能退出,退出应用就会闪退了。
    while(mPlayingState != PLAYING_STATE_STOPPED) {};
}

2. producePacket回调获取buffer数据

void SoundService::producePacket() {
    LOGI("SoundService::producePacket() audio player call back method...");
    byte *audioBuffer = mBuffer + (mCurrentFrame % mBufferNums) * mPacketBufferSize;
    int result = 0;
    if (NULL != mDecoderController) {
        /*for (int j = 0; j < 33; j++) {
            int pSize = -1;
            pSize = mDecoderController->readSamples(mTarget + result, mPacketBufferSize/2, NULL);
            if (0 < pSize) {
                result += pSize;
            } else {
                break;
            }
        } */
        // 读取数据
        result = mDecoderController->readSamples(mTarget + result, mPacketBufferSize/2, NULL);
        LOGI("enter SoundService::producePacket() PLAYING_STATE_PLAYING packetBufferSize=%d, result=%d", mPacketBufferSize, result);
    }
    if (0 < result) {
        convertByteArrayFromShortArray(mTarget, result, audioBuffer);
        // 入栈
        (*mAudioPlayerBufferQueue)->Enqueue(mAudioPlayerBufferQueue, audioBuffer, result * 2);
    }
    mCurrentFrame = (mCurrentFrame + 1) % mBufferNums;
}

3. stop停止播放

JNIEXPORT void JNICALL Java_com_example_audiotracktest_opensles_SoundTrackController_stop
(JNIEnv * env, jobject obj) {
    if (NULL != soundService) {
        // 1. 停止播放
        soundService->stop();
        soundService = NULL;
    }
}

1. stop停止播放

SLresult SoundService::stop() {
    LOGI("enter SoundService::stop()");
    mPlayingState = PLAYING_STATE_STOPPED;
    LOGI("Set the audio player state paused");
    // 1. 设置audioplayer的状态为stoped
    // (*mAudioPlayerPlay)->SetPlayState(mAudioPlayerPlay, SL_PLAYSTATE_PAUSED);
    SLresult result = SetAudioPlayerStateStoped();
    if (SL_RESULT_SUCCESS != result) {
        LOGI("Set the audio player state paused return false");
        return result;
    }
    // 2. 销毁object
    DestroyContext();
    LOGI("out SoundService::stop()");
}
void SoundService::DestroyContext() {
    LOGI("enter SoundService::DestroyContext");
    DestroyObject(mAudioPlayerObject);
    FreePlayerBuffer();
    DestroyObject(mOutputMixObject);
    if (NULL != mDecoderController) {
        mDecoderController->destroy();
        delete mDecoderController;
        mDecoderController = NULL;
    }

    LOGI("leave SoundService::DestroyContext");
}

问题

1. SL_RESULT_BUFFER_INSUFFICIENT错误

SLDataLocator_AndroidSimpleBufferQueue配置的buffer数和play的时候Enqueue的buffer数目对不上

2. play函数中出现段错误

play函数不应该退出,不然就会出现段错误。在最后面加个while死循环。

参考

(1)Android音频开发(7):使用 OpenSL ES API(下):
https://blog.51cto.com/ticktick/1771239
(2)Android Open SL ES — 官方Demo解析native-audio
https://www.jianshu.com/p/5308ab38a3d0
(3)源码位置:https://github.com/mashenlyl/AudioTrackTest
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容