音视频:13.FFmpeg-音乐播放器2

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

1.解决内存上涨的问题

将循环中新建数组操作提到循环外面去

    
    jbyteArray jPcmByteArray =  env->NewByteArray(dataSize);
    // native 创建 c 数组
    jbyte *jPcmData =  env->GetByteArrayElements(jPcmByteArray, NULL);

    pPacket = av_packet_alloc();
    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
                    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(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);
    // 0 把 c 的数组的数据同步到 jbyteArray,然后释放native数组
    env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, 0);
    // 解除 jPcmDataArray 的持有,让 javaGC 回收
    env->DeleteLocalRef(jPcmByteArray);

2.解决杂音问题

分析

  • 原因:是因为音频的的采样率和采用格式跟我们使用AudioTrack播放设置的会不一致。
  • 解决:使用重采样
  // --------------- 重采样 start --------------
    //输出的声道布局(立体声)
    int64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //输出采样格式16bit PCM
    enum AVSampleFormat out_sample_fmt = AVSampleFormat::AV_SAMPLE_FMT_S16;
    //输出采样率
    int out_sample_rate = AUDIO_SAMPLE_RATE;
    //获取输入的声道布局
    //根据声道个数获取默认的声道布局(2个声道,默认立体声stereo)
    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) {
        LOGE("swr alloc set opts error");
        callPlayerJniError(SWR_ALLOC_SET_OPTS_ERROR_CODE, "swr alloc set opts error");
        // 提示错误
        return;
    }
    int swrInitRes = swr_init(swrContext);
    if (swrInitRes < 0) {
        LOGE("swr context swr init error");
        callPlayerJniError(SWR_CONTEXT_INIT_ERROR_CODE, "swr context swr init error");
        return;
    }
    // size 是播放指定的大小,是最终输出的大小
    int outChannels = av_get_channel_layout_nb_channels(out_ch_layout);
    int dataSize = av_samples_get_buffer_size(NULL, outChannels, pCodecParameters->frame_size,
                                              out_sample_fmt, 0);
    resampleOutBuffer = (uint8_t *) malloc(dataSize);
    // --------------- 重采样 end --------------

    ....

    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);
                    
                    ....

                    memcpy(jPcmData, resampleOutBuffer, dataSize);
                    // 1 把 c 的数组的数据同步到 jbyteArray,然后不释放native数组
                    env->ReleaseByteArrayElements(jPcmByteArray, jPcmData, JNI_COMMIT);
                    pJniCall->callAudioTrackWrite(jPcmByteArray, 0, dataSize);
                }
            }
        }
        // 解引用
        av_packet_unref(pPacket);
        av_frame_unref(pFrame);
    }

3. 添加错误回调到 Java


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



void JNICall::callPlayerError(int code, char *msg) {
    jstring jMsg = jniEnv->NewStringUTF(msg);
    jniEnv->CallVoidMethod(jPlayerObj, jPlayerErrorMid, code, jMsg);
    jniEnv->DeleteLocalRef(jMsg);
}

4.多线程解码播放

void *threadPlay(void *arg) {
    FFmpeg *pFFmpeg = (FFmpeg *) arg;
    pFFmpeg->prepare();
    return NULL;
}

void FFmpeg::play() {
    // 创建一个线程去播放,多线程边解码边播放
    pthread_t tid;
    pthread_create(&tid,NULL,threadPlay,this);
    pthread_detach(tid); // 回收线程
}

5.ffmpeg的一些常用指令

ffmpeg 命令
转格式
ffmpeg -i input.mp4 -vcodec copy -acodec copy out.flv
抽取视频
ffmpeg -i input.mp4 -an -vcodec copy out.h264
抽取音频
ffmpeg -i input.mp4 -vn -acodec copy out.aac
提取 YUV 
ffmpeg -i input.mp4 -an -c:v rawvideo -pix_fmt yuv420p out.yuv
提取 PCM
ffmpeg -i input.mp4 -vn -ar 44100 -ac2 -f s16le out.pcm
视频裁剪
ffmpeg -i input.mp4 -vf corp=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4
视频裁剪
ffmpeg -i input.mp4 -ss 00:00:00 -t 10 out.mp4
视频转图片
ffmpeg -i input.mp4 -r 1 -f image2 image-%3d.jpeg
图片转视频
ffmpeg -i image-%3d.jpeg out.mp4
直播推流
ffmpeg -re -i output.mp4 -c copy -f flv rtmp://server/live/streamName
直播拉流
ffmpeg -i rtmp://server/live/streamName -c copy dump.flv

问题:子线程中返回 java 错误会有问题

猜你喜欢

转载自blog.csdn.net/q925092273/article/details/109644537