美文网首页FFmpegFFmpeg音视频开发NDK开发
Android使用FFmpeg(四)--ffmpeg实现音频播放

Android使用FFmpeg(四)--ffmpeg实现音频播放

作者: 2012lc | 来源:发表于2017-11-15 10:20 被阅读1052次

    关于

    Android使用FFmpeg(一)--编译ffmpeg
    Android使用FFmpeg(二)--Android Studio配置ffmpeg
    Android使用FFmpeg(三)--ffmpeg实现视频播放
    Android使用FFmpeg(四)--ffmpeg实现音频播放(使用AudioTrack进行播放)
    Android使用FFmpeg(五)--ffmpeg实现音频播放(使用openSL ES进行播放)
    Android使用FFmpeg(六)--ffmpeg实现音视频同步播放
    Android使用FFmpeg(七)--ffmpeg实现暂停、快退快进播放

    准备工作

    jni调用Java方法
    AudioTrack使用
    关于音频

    正文

    ffmpeg实现音频播放大概流程图.png

    和视频播放一样我们依照流程图来实现,从这个流程图可以看出,音频播放和视频播放的大概流程并没有多大差别,只是细节之处需要处理。

    依照流程来实现

    1.注册组件,打开音频文件并获取内容,找到音频流:

    av_register_all();
        AVFormatContext *pFormatCtx = avformat_alloc_context();
        //open
        if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {
            LOGE("%s","打开输入视频文件失败");
            return;
        }
        //获取视频信息
        if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
            LOGE("%s","获取视频信息失败");
            return;
        }
        int audio_stream_idx=-1;
        int i=0;
        for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
            if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
                LOGE("  找到音频id %d", pFormatCtx->streams[i]->codec->codec_type);
                audio_stream_idx=i;
                break;
            }
        }
    

    2.获取解码器,申请avframe和avpacket:

     //获取解码器上下文
        AVCodecContext *pCodecCtx=pFormatCtx->streams[audio_stream_idx]->codec;
        //获取解码器
        AVCodec *pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
        //打开解码器
        if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
        }
        //申请avpakcet,装解码前的数据
        AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
        //申请avframe,装解码后的数据
        AVFrame *frame = av_frame_alloc();
    

    3.初始化SwrContext,进行重采样

    //得到SwrContext ,进行重采样,具体参考http://blog.csdn.net/jammg/article/details/52688506
        SwrContext *swrContext = swr_alloc();
        //缓存区
        uint8_t *out_buffer = (uint8_t *) av_malloc(44100 * 2);
    //输出的声道布局(立体声)
        uint64_t  out_ch_layout=AV_CH_LAYOUT_STEREO;
    //输出采样位数  16位
        enum AVSampleFormat out_formart=AV_SAMPLE_FMT_S16;
    //输出的采样率必须与输入相同
        int out_sample_rate = pCodecCtx->sample_rate;
    
    //swr_alloc_set_opts将PCM源文件的采样格式转换为自己希望的采样格式
        swr_alloc_set_opts(swrContext, out_ch_layout, out_formart, out_sample_rate,
                           pCodecCtx->channel_layout, pCodecCtx->sample_fmt, pCodecCtx->sample_rate, 0,
                           NULL);
        swr_init(swrContext);
    

    4.通过while循环读取内容,并通过AudioTrack进行播放:

    //    获取通道数  2
        int out_channer_nb = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
    //    反射得到Class类型
        jclass david_player = env->GetObjectClass(instance);
    //    反射得到createAudio方法
        jmethodID createAudio = env->GetMethodID(david_player, "createTrack", "(II)V");
    //    反射调用createAudio
        env->CallVoidMethod(instance, createAudio, 44100, out_channer_nb);
        jmethodID audio_write = env->GetMethodID(david_player, "playTrack", "([BI)V");
    int got_frame;
        while (av_read_frame(pFormatCtx, packet) >= 0) {
            if (packet->stream_index == audio_stream_idx) {
    //            解码  mp3   编码格式frame----pcm   frame
                avcodec_decode_audio4(pCodecCtx, frame, &got_frame, packet);
                if (got_frame) {
                    LOGE("解码");
                    swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) frame->data, frame->nb_samples);
    //                缓冲区的大小
                    int size = av_samples_get_buffer_size(NULL, out_channer_nb, frame->nb_samples,
                                                          AV_SAMPLE_FMT_S16, 1);
                    jbyteArray audio_sample_array = env->NewByteArray(size);
                    env->SetByteArrayRegion(audio_sample_array, 0, size, (const jbyte *) out_buffer);
                    env->CallVoidMethod(instance, audio_write, audio_sample_array, size);
                    env->DeleteLocalRef(audio_sample_array);
                }
            }
        }
    

    5.释放需要释放的资源:

        av_frame_free(&frame);
        swr_free(&swrContext);
        avcodec_close(pCodecCtx);
        avformat_close_input(&pFormatCtx);
        env->ReleaseStringUTFChars(input_, input);
    

    java层创建AudioTrack方法。

    jni通过调用java层的audiotrack方法来实现播放,但是java层的audiotrack方法又是通过调用底层的openesl es来进行播放,相当于绕了一个圈,所以在下篇文章中将实现使用opensl es来直接播放音频。

    public class MusicPlay {
        static{
            System.loadLibrary("avcodec-56");
            System.loadLibrary("avdevice-56");
            System.loadLibrary("avfilter-5");
            System.loadLibrary("avformat-56");
            System.loadLibrary("avutil-54");
            System.loadLibrary("postproc-53");
            System.loadLibrary("swresample-1");
            System.loadLibrary("swscale-3");
            System.loadLibrary("native-lib");
        }
        public native void playSound(String input);
    
        private AudioTrack audioTrack;
        //    这个方法  是C进行调用 
        public void createTrack(int sampleRateInHz,int nb_channals) {
            int channaleConfig;//通道数
            if (nb_channals == 1) {
                channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
            } else if (nb_channals == 2) {
                channaleConfig = AudioFormat.CHANNEL_OUT_STEREO;
            }else {
                channaleConfig = AudioFormat.CHANNEL_OUT_MONO;
            }
            int buffersize=AudioTrack.getMinBufferSize(sampleRateInHz,
                    channaleConfig, AudioFormat.ENCODING_PCM_16BIT);
            audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRateInHz,channaleConfig,
                    AudioFormat.ENCODING_PCM_16BIT,buffersize,AudioTrack.MODE_STREAM);
            audioTrack.play();
        }
        //C传入音频数据
        public void playTrack(byte[] buffer, int lenth) {
            if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
                audioTrack.write(buffer, 0, lenth);
            }
        }
    }
    

    小结

    按照流程,使用AudioTrack播放音频就是这么回事。通过和上篇的播放视频相比较,流程就差不多了:注册->解封装->获取流->解码->播放。
    源码地址

    相关文章

      网友评论

        本文标题:Android使用FFmpeg(四)--ffmpeg实现音频播放

        本文链接:https://www.haomeiwen.com/subject/unwpmxtx.html