之前已经完成了视频的播放,下面将完成音频的播放。

一、音频解码

1、首先在fFpeng类中会开启一个不断读取packet的子线程,并且把相应packet类型(视频类型,音频类型)存放到不同的音视频frame队列中。和之前视频播放一样,这里添加音频的代码。代码如下:

void QbFFmpeg::start() {
    isPlaying = true;
    //视频开始播放 交给视频对象处理
    if (videoChannel2) {
        videoChannel2->play();
    }

    //音频开始播放 交给音频对象处理
    if(audioChannel){
        audioChannel->play();
    }

    //开始解码线程
    pthread_create(&playThread, NULL, play, this);
}

2、调用audioChannel->play()的方法,代码如下:

void *openSLInit(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->initOpenSLES();
    return 0;
}

void *decodeAudio(void *args) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(args);
    audioChannel->decodePackets();
    return 0;
}

void AudioChannel::play() {
    //将音频数据转为pcm格式。因为喇叭播放的格式要统一为pcm格式 比如aac和MP3格式他们的编码格式是不一样的转,需要转换为统一格式

    //声明转换上下文
    swr_ctx = swr_alloc_set_opts(0, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, out_sample_rate,
                                 avCodecContext->channel_layout,
                                 avCodecContext->sample_fmt,
                                 avCodecContext->sample_rate, 0, 0);
    //初始化转换上下文
    swr_init(swr_ctx);


    pkt_queue.setWork(1);
    frame_queue.setWork(1);
    isPlaying = true;
    //创建初始化openSL ES 线程
    pthread_create(&pid_opensl_init, NULL, openSLInit, this);
    //创建音频解码线程
    pthread_create(&pid_opensl_init, NULL, decodeAudio, this);
}

3、调用play方法后,会启动两个线程,初始化openSLES线程和音频解码线程。初始化代码如下:

/**
 * 子线程中初始化openSL ES 分为6个步骤
 */
void AudioChannel::initOpenSLES() {
    //音频引擎
    SLEngineItf engineInterface = NULL;
    //音频对此昂
    SLObjectItf engineObject = NULL;
    //混音器
    SLObjectItf outputMixObject = NULL;

    //播放器
    SLObjectItf bqPlayerObject = NULL;
//    回调接口
    SLPlayItf bqPlayerInterface = NULL;
//    缓冲队列
    SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL;

    //todo 第一步、初始化播放引擎
    SLresult result;
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //音频接口  相当于SurfaceHodler
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }

    //todo 第二步、设置混音器
    result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject, 0, 0, 0);
    // 初始化混音器outputMixObject
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
    if (SL_RESULT_SUCCESS != result) {
        return;
    }
    //todo 第三步、创建播放器
    SLDataLocator_AndroidSimpleBufferQueue android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
                                                            2};
    //pcm数据格式
    SLDataFormat_PCM pcm = {SL_DATAFORMAT_PCM//播放pcm格式的数据
            , 2,//2个声道(立体声)
                            SL_SAMPLINGRATE_44_1, //44100hz的频率
                            SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16位
                            SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行
                            SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)
                            SL_BYTEORDER_LITTLEENDIAN//小端模式
    };
    //
    SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&outputMix, NULL};
    SLDataSource slDataSource = {&android_queue, &pcm};
    const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
    const SLboolean req[1] = {SL_BOOLEAN_TRUE};
    (*engineInterface)->CreateAudioPlayer(engineInterface, &bqPlayerObject// //播放器
            , &slDataSource//播放器参数  播放缓冲队列   播放格式
            , &audioSnk,//播放缓冲区
                                          1,//播放接口回调个数
                                          ids,//设置播放队列ID
                                          req//是否采用内置的播放队列
    );
    //初始化播放器
    (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);

    //todo 第四步、设置缓存队列和回调函数
//    得到接口后调用  获取Player接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerInterface);
//    获得播放器接口
    (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
                                    &bqPlayerBufferQueue);
    (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, this);
    // todo 第五步、设置播放状态,需要手动进行设置
    (*bqPlayerInterface)->SetPlayState(bqPlayerInterface, SL_PLAYSTATE_PLAYING);
    //todo 第六步、启动回调函数 只要启动就会不断的被回调 把pcm数据缓存到缓冲区中
    bqPlayerCallback(bqPlayerBufferQueue, this);
    LOGE("--- 手动调用播放 packet:%d", this->pkt_queue.size());
}

/**
 * 音频解码
 */
void AudioChannel::decodePackets() {
    AVPacket *packet = 0;
    while (isPlaying) {
        //从队列中取一个
        int ret = pkt_queue.deQueue(packet);
        if (!isPlaying) {
            break;
        }
        if (!ret) {
            continue;
        }

        ret = avcodec_send_packet(avCodecContext, packet);
        releaseAvPacket(packet);
        if (ret == AVERROR(EAGAIN)) {
            //需要更多数据
            continue;
        } else if (ret < 0) {
            //失败
            break;
        }
        AVFrame *frame = av_frame_alloc();
        ret = avcodec_receive_frame(avCodecContext, frame);
        if (ret == AVERROR(EAGAIN)) {
            //需要更多数据
            continue;
        } else if (ret < 0) {
            break;
        }
        while (frame_queue.size() > 100 && isPlaying) {
            av_usleep(1000 * 10);
            continue;
        }
        //packet包解码后放入帧队列
        frame_queue.enQueue(frame);

    }
}

4、OpenSL使用回调机制来访问音频IO,但不像跟Jack、CoreAudio那些音频异步IO框架,OpenSL 的回调里并不会把音频数据作为参数传递,回调方法仅仅是告诉我们:BufferQueue已经就绪,可以接受/获取数据了。这里的回调函数是bqPlayerCallback(bqPlayerBufferQueue, this);会不断地从bufferQueue中获取音频数据,交给喇叭播放。代码如下:

/**
 * openSLES回调函数
 */

void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
    AudioChannel *audioChannel = static_cast<AudioChannel *>(context);

    //获取pcm缓冲队列大小
    int dataLength = audioChannel->changeFrameToPcm();
    if(dataLength >0 ){
        //送入音频播放队列播放 喇叭会从队列中主动去拿这些数据进行播放
        (*bq)->Enqueue(bq,audioChannel->buffer,dataLength);
    }
}

5、上面首先将从buffer中去除的数据传为可播放的pcm数据。因为对于不同格式的数据(比如aac格式、MP4格式)所支持的播放设备是不一样的,只有将他们转为统一的pcm数据格式才能进行播放。转化代码如下:

/**
 * 转换为pcm数据
 */
int AudioChannel::changeFrameToPcm() {
    //播放的时候才需要转换
    AVFrame *frame = 0;
    int data_size = 0;
    while (isPlaying) {
        int ret = frame_queue.deQueue(frame);
        if (!isPlaying) {
            break;
        }
        if (!ret) {
            continue;
        }
        //设置转换的一些参数
        uint64_t dst_nb_samples = av_rescale_rnd(
                swr_get_delay(swr_ctx, frame->sample_rate) + frame->nb_samples,
                out_sample_rate,
                frame->sample_rate,
                AV_ROUND_UP);
        // 转换,返回值为转换后的sample个数  buffer malloc(size) 转换的数据放到buffer中
        int nb = swr_convert(swr_ctx, &buffer, dst_nb_samples,
                             (const uint8_t **) frame->data, frame->nb_samples);
        //计算buffer大小
        data_size = nb * out_channels * out_samplesize;
        //数量*单位 pts是数量
        clock = frame->pts*av_q2d(time_base);
        break;
    }
    releaseAvFrame(frame);
    return data_size;
}

6、集成以上代码,第4步(*bq)->Enqueue(bq,audioChannel->buffer,dataLength);执行以后,函数往音频设备放入数据,就能实现播放了。