关闭

FFMPEG 解码音频

标签: ffmpeg数据
236人阅读 评论(1) 收藏 举报
分类:

目的

通过FFMPEG解码音频的码流,得到PCM的音频采样数据并用AudioTraker播放

步骤

1.注册所有组件

av_register_all();

2.拿到封装格式上下文

AVFormatContext *avFormatContext = avformat_alloc_context();

3 打开文件

    if (avformat_open_input(&avFormatContext, src_cstr, NULL, NULL) < 0) {
        LOGE("%s", "打开文件失败");
        return; 
    }

4.查找流信息

avformat_find_stream_info(avFormatContext, NULL)

5.拿到解码器

avcodec_find_decoder(avCodecCtx->codec_id)

6.打开解码器

avcodec_open2(avCodecCtx, aVCodec, NULL)

7.读取一帧一帧的压缩音频数据

av_read_frame(avFormatContext, aVPacket)

8.开始解码

vcodec_decode_audio4(avCodecCtx, aVFrame, &got_frame_ptr,aVPacket)

9.解码得到的Frame数据,转成PCM

swr_convert(swrCtx, &outBuffer, MAX_AUDIO_FRME_SIZE,
aVFrame->data, aVFrame->nb_samples)

10.调用java端的方法,获取到AudioTraker对象

//java代码
public AudioTrack createAudio() {

    int sampleRateInHz = 44100;
    int channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
    int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
    int minBufferSize = AudioTrack.getMinBufferSize(sampleRateInHz,
            channelConfig, audioFormat);

    AudioTrack audio = new AudioTrack(AudioManager.STREAM_MUSIC, // 指定流的类型
            sampleRateInHz, // 设置音频数据的采样率 32k,如果是44.1k就是44100
            channelConfig, // 设置输出声道为双声道立体声,而CHANNEL_OUT_MONO类型是单声道
            audioFormat, // 设置音频数据块是8位还是16位,这里设置为16位。好像现在绝大多数的音频都是16位的了
            minBufferSize, AudioTrack.MODE_STREAM // 设置模式类型,在这里设置为流类型,另外一种MODE_STATIC貌似没有什么效果
    );
    // audio.play(); // 启动音频设备,下面就可以真正开始音频数据的播放了
    return audio;
}

// jni调用createAudio
jclass player_class = (*env)->GetObjectClass(env, jthis);
jmethodID createAudioMId = (*env)->GetMethodID(env, player_class,
        "createAudio", "()Landroid/media/AudioTrack;");

//JNIEnv*, jobject, jmethodID, ...
jobject audioTraker = (*env)->CallObjectMethod(env, jthis, createAudioMId);

11.调用AudioTrack.paly(),write()播放解码后的音频数据

javap反编译可以得到方法签名

// 调用play
jclass audio_class = (*env)->GetObjectClass(env, audioTraker);
jmethodID playMId = (*env)->GetMethodID(env, audio_class, "play", "()V");
(*env)->CallVoidMethod(env, audioTraker, playMId);
jmethodID writeMId = (*env)->GetMethodID(env, audio_class, "write",
        "([BII)I");

        .....

// while循环调用write(byte[] audioData, int offsetInBytes, int sizeInBytes)
//这里需要byte[],所以构造byteArray
jbyteArray byteArray = (*env)->NewByteArray(env, bufferSize);
jbyte* byteElement = (*env)->GetByteArrayElements(env,
        byteArray, NULL);

//out_buffer的数据复制到sampe_bytep
memcpy(byteElement, outBuffer, bufferSize);
// 同步byteElement数据到byteArray 
(*env)->ReleaseByteArrayElements(env, byteArray, byteElement,
        0);

//调用AudioTraker的write()方法
(*env)->CallIntMethod(env, audioTraker,writeMId,byteArray,
        0, bufferSize);
//在while循环里面不断创建对象,需要释放局部引用
(*env)->DeleteLocalRef(env, byteArray);

经过以上步骤可以实现文件的音频解码!

代码

/**
 * 解码音频文件并用AudioTraker进行播放
 */JNIEXPORT void JNICALL Java_com_example_ffmpeg_AudioUtil_convertAudio(
        JNIEnv *env, jobject jthis, jstring src_jstr, jstring dst_jstr) {

    const char* src_cstr = (*env)->GetStringUTFChars(env, src_jstr, NULL);
    const char* dst_cstr = (*env)->GetStringUTFChars(env, dst_jstr, NULL);

    //注册所有组件
    av_register_all();
    //拿到封装格式上下文
    AVFormatContext *avFormatContext = avformat_alloc_context();
    // 打开文件
    if (avformat_open_input(&avFormatContext, src_cstr, NULL, NULL) < 0) {

        LOGE("%s", "打开文件失败");
        return;
    }

    if (avformat_find_stream_info(avFormatContext, NULL) < 0) {

        LOGE("%s", "获取流信息失败");
        return;
    }
    // 拿到解码器
    int index = -1;
    int i = 0;
    AVCodecContext * avCodecCtx;

    for (; i < avFormatContext->nb_streams; i++) {

        avCodecCtx = avFormatContext->streams[i]->codec;
        if (avCodecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {

            index = i;
            break;
        }
    }

    if (index == -1) {

        LOGE("%s", "没有找到索引");
        return;
    }

    AVCodec *aVCodec = avcodec_find_decoder(avCodecCtx->codec_id);
    if (aVCodec == NULL) {

        LOGE("%s", "没有找到解码器");
        return;
    }
    // 打开解码器
    if (avcodec_open2(avCodecCtx, aVCodec, NULL) < 0) {

        LOGE("%s", "打开解码器失败");
        return;
    }

    // 拿到数据帧,开始解码
    AVFrame *aVFrame = av_frame_alloc();
    int got_frame_ptr;
    AVPacket *aVPacket = (AVPacket*) av_malloc(sizeof(AVPacket));

    struct SwrContext *swrCtx = swr_alloc();
    // 输出的缓冲区大小
    uint8_t *outBuffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);

    // 输出声道布局
    int64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 44100;
    // 输入的声道布局
    int64_t in_ch_layout = avCodecCtx->channel_layout;
    enum AVSampleFormat in_sample_fmt = avCodecCtx->sample_fmt;
    int in_sample_rate = avCodecCtx->sample_rate;
    int nb_channels = av_get_channel_layout_nb_channels(out_ch_layout);
    // 设置转换的参数选项
    swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate,
            in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);
    swr_init(swrCtx);
    int lenCount = 0;

    LOGE("%s", "HAAHAHAHH");
    // 调用createAudio
    jclass player_class = (*env)->GetObjectClass(env, jthis);
    jmethodID createAudioMId = (*env)->GetMethodID(env, player_class,
            "createAudio", "()Landroid/media/AudioTrack;");

    LOGE("createAudioMId%d", createAudioMId);
    //JNIEnv*, jobject, jmethodID, ...
    jobject audioTraker = (*env)->CallObjectMethod(env, jthis, createAudioMId);
    LOGE("audioTraker%d", audioTraker);
    // 调用play
    jclass audio_class = (*env)->GetObjectClass(env, audioTraker);
    jmethodID playMId = (*env)->GetMethodID(env, audio_class, "play", "()V");
    (*env)->CallVoidMethod(env, audioTraker, playMId);
    jmethodID writeMId = (*env)->GetMethodID(env, audio_class, "write",
            "([BII)I");
    while (av_read_frame(avFormatContext, aVPacket) >= 0) {
        // 注意,这里我们只需要解音频的压缩数据
        if (aVPacket->stream_index == index) {

            int len = avcodec_decode_audio4(avCodecCtx, aVFrame, &got_frame_ptr,
                    aVPacket);
            LOGE("解码%d帧", lenCount++);
            if (len < 0) {

                LOGE("%s", "解码完成");
            }
            // frame-->16bit PCM数据,写入文件
            if (got_frame_ptr) {

                // number of samples output per channel
                swr_convert(swrCtx, &outBuffer, MAX_AUDIO_FRME_SIZE,
                        aVFrame->data, aVFrame->nb_samples);
                int bufferSize = av_samples_get_buffer_size(NULL, nb_channels,
                        aVFrame->nb_samples, out_sample_fmt, 1);

                // 调用write
                //byte[] audioData, int offsetInBytes, int sizeInBytes
                jbyteArray byteArray = (*env)->NewByteArray(env, bufferSize);
                jbyte* byteElement = (*env)->GetByteArrayElements(env,
                        byteArray, NULL);

                //out_buffer的数据复制到sampe_bytep
                memcpy(byteElement, outBuffer, bufferSize);
                (*env)->ReleaseByteArrayElements(env, byteArray, byteElement,
                        0);
//              (*env)->CallVoidMethod(env, audioTraker, writeMId, byteElement,
//                                      0, bufferSize);

                (*env)->CallIntMethod(env, audioTraker,writeMId,byteArray,
                        0, bufferSize);
                //释放局部引用
                (*env)->DeleteLocalRef(env, byteArray);
                usleep(1000 * 16);
            }
        }
        av_free_packet(aVPacket);
    }

    swr_free(&swrCtx);
    av_frame_free(&aVFrame);
    av_free(outBuffer);
    avformat_free_context(avFormatContext);
    avcodec_free_context(&avCodecCtx);
    (*env)->ReleaseStringUTFChars(env, src_jstr, src_cstr);
    (*env)->ReleaseStringUTFChars(env, dst_jstr, dst_cstr);
}
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    最新评论