FFMPEG 解码音频

原创 2017年01月03日 21:51:13

目的

通过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);
}

相关文章推荐

FFMPEG音频解码浅析

转自:http://blog.csdn.net/xiaozhu1100/article/details/16929181 结合各种资料和自己的理解,估计有些浅显。 FFMPEG...

最简单的基于FFmpeg的内存读写的例子:内存播放器

打算记录两个最简单的FFmpeg进行内存读写的例子。之前的所有有关FFmpeg的例子都是对文件进行操作的。例如《最简单的基于FFmpeg+SDL的视频播放器》播放的是一个视频的文件。而《最简单的基于F...

FFmpeg 学习之 Mediainfo 读取多信道 TS 视频码流

FFmpeg 解码过程中,有以下几个参数 AVFormatContext 结构体中有一个 ts_id 变量,表示文件 ID,固定值。 AVStream 结构体中有一个 id 变量,表示 FFmpe...

Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)

在Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)中我们从视频文件中解码出音频,这一章中将使用OpenSL ES来播放解码的音频数据,首先关于OpenSL ES这里暂不介...

ffplay播放视频源延时的参数设置

使用ffplay播放视频源时,rtsp/rtmp等,会有一定的延时,这里我们可以通过设置ffplay播放参数将延时控制到最小。 ffplay.exe -i rtmp://xxxxxxx -ffla...

FFmpeg_13_音频解码

  • 2015年08月14日 17:23
  • 16.45MB
  • 下载

FFMPEG视音频编解码零基础学习方法

这篇文章是nayong 在CSDN上的这一段日子,接触到了很多同行业的人,尤其是使用FFMPEG进行视音频编解码的人,有的已经是有多年经验的“大神”,有的是刚开始学习的初学者。在和大家探讨的过程...

ffmpeg解码音频并保存PCM的简单demo

  • 2016年11月08日 23:39
  • 19.33MB
  • 下载

Android+FFmpeg+OpenSL ES音频解码播放

  • 2016年03月21日 12:45
  • 39.19MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:FFMPEG 解码音频
举报原因:
原因补充:

(最多只允许输入30个字)