音视频:14.FFmpeg-音乐播放器3

具体代码请看:NDKPractice项目的ffmpeg83

1.添加准备完毕回调

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

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


void JNICall::CallPlayerPrepared(ThreadMode threadMode) {
    // 子线程(pThread)用不了主线程(native线程)的 jniEnv
    // 子线程是不共享 jniEnv,他们有自己所独有的
    if (threadMode == THREAD_MAIN) {
        jniEnv->CallVoidMethod(jPlayerObj, jPlayerPreparedMid);
    } else {
        // 通过 JavaVM获取当前线程的 JniEnv
        JNIEnv *env;
        if (javaVM->AttachCurrentThread(&env, 0) != JNI_OK) {
            LOGE("get child thread jniEnv error");
            return;
        }
        env->CallVoidMethod(jPlayerObj, jPlayerPreparedMid);
        javaVM->DetachCurrentThread();
    }
}

2.用队列边解码边播放

为什么要用队列边解码边播放?

原因:首先解码成pcm是耗时的,如果播放的网络音频,网络卡顿时读取也会耗时,这个时候读取本来就耗时的情况下每次读取成功还等待解码完成后再去读取就会有点卡顿。所以咱们读取和解码分开就会提升性能

av_read_frame:从流里面解析一个一个的 packet ,流可能是本地流也可以是网络流

 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
                        index++;
                        LOGE("解码第 %d 帧", index);

                        // 调用重采样的方法
                        swr_convert(swrContext, &resampleOutBuffer, pFrame->nb_samples,
                                    (const uint8_t **) pFrame->data, pFrame->nb_samples);

                        // write 写到缓冲区 pFrame.data -> javabyte
                        // size 是多大,装 pcm 的数据
                        // 1s 44100 点,2通道, 2字节 44100*2*2
                        // 1帧不是一秒,pFrame->nb_samples点

                        memcpy(jPcmData, resampleOutBuffer, dataSize);
                        // 1 把 c 的数组的数据同步到 jbyteArray,然后不释放native数组
                        env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);

                        pJniCall->callAudioTrackWrite(env,jPcmByteArray, 0, dataSize);
                    }
                }
                //--------------------- 解码结束 -------------------------/

            }
            // 解引用
            av_packet_unref(pPacket);
            av_frame_unref(pFrame);
        }

        // 1.解引用数据 data, 2.销魂 pPacket 结构体内存, 3.pPacket = NULL;
        av_packet_free(&pPacket);
        av_frame_free(&pFrame);

修改过后,具体请看文件Audio.cpp

// 读取的线程
void *threadReadPacket(void *args){
    Audio *pAudio = (Audio*)args;
    while(pAudio->pPlayerStatus != NULL && !pAudio->pPlayerStatus->isExit){
       AVPacket *pPacket = av_packet_alloc();
        // 循环从上下文中读取帧到包中
        if (av_read_frame(pAudio->pFormatContext, pPacket) >= 0) {
            if (pPacket->stream_index == pAudio->audioStreamIndex) {
                // 读取音频压缩包数据后,将其push 到 队列中
                pAudio->pPacketQueue->push(pPacket);
            }else{
                // 解引用
                av_packet_unref(pPacket);
            }
        }else{
            // 1.解引用数据 data, 2.销魂 pPacket 结构体内存, 3.pPacket = NULL;
            av_packet_free(&pPacket);
            // 睡眠一下,尽量不去消耗 cpu 的资源,也可以退出销毁这个线程
            // break;
        }
    }
    return nullptr;
}

// 解码的线程 OpenSLES
int Audio::resampleAudio() {
    int dataSize = 0;
    AVPacket *pPacket = nullptr;
    AVFrame *pFrame = av_frame_alloc();

    while (pPlayerStatus != nullptr && !pPlayerStatus->isExit) {
        pPacket = pPacketQueue->pop();
        // Packet 包,压缩的数据,解码成 pcm 数据
        int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
        if (codecSendPacketRes == 0) {
            int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
            if (codecReceiveFrameRes == 0) {
                // AVPacket -> AVFrame
                // 调用重采样的方法,返回值是返回重采样的个数,也就是 pFrame->nb_samples
                dataSize = swr_convert(pSwrContext, &resampleOutBuffer, pFrame->nb_samples,
                                       (const uint8_t **) pFrame->data, pFrame->nb_samples);
                LOGE("解码音频帧:%d %d",dataSize,pFrame->nb_samples);

                dataSize = pFrame->nb_samples * 2 * 2; // 采样率 * 通道数 * 两字节
                // 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值