专栏首页包子的书架FFmpeg进行音频的解码和播放

FFmpeg进行音频的解码和播放

音频编码

音频数字化主要有压缩与非压缩(pcm)两种方式。

  • 非压缩编码(PCM)PCM音频编码 PCM通过抽样、量化、编码三个步骤将连续变化的模拟信号转换为数字编码。 当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的2.56~4倍;采样定理又称奈奎斯特定理。 PCM信号未经过任何编码和压缩处理, 声音之所以能够数字化,是因为人耳所能听到的声音频率不是无限宽的,主要在20kHz以上。按照抽样定理,只有抽样频率大于40kHz,才能无失真地重建原始声音。如CD采用44.1kHz的抽样频率,其他则主要采用48kHz或96kHz。
  • 压缩编码 PCM虽然为无损压缩,但由典型的音频信号表示的信号特性没有达到最佳,也没有很好的适应人耳听觉系统的特定要求。PCM的数据量过高,从而造成存储和传输方面的障碍,因此必须使用相应的技术降低数字信号源的数据率,又尽可能不对节目造成损伤,这就是压缩技术 常见的压缩的音频格式WAV,MP3。 WAV格式,是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持,压缩率低。 MP3全称是MPEG-1 Audio Layer 3,它在1992年合并至MPEG规范中。MP3能够以高音质、低采样率对数字音频文件进行压缩。应用最普遍。

FFmpeg 解码音频文件

上一篇FFmpeg 内容介绍 音视频解码和播放 介绍了FFmpeg进行解码的常见函数和,解码的过程。相关的函数介绍忘记了,可以参考上一篇。

  • 直接核心贴代码 实现功能:将mp3、wav等格式转成pcm
    // 源文件路径
    const char * src_path = env->GetStringUTFChars(src_audio_path, NULL);
    // 生产的pcm文件路径
    const char * dst_path = env->GetStringUTFChars(dst_audio_path, NULL);
    // AVFormatContext 对象创建
    AVFormatContext *avFormatContext = avformat_alloc_context();
   // 打开音频文件
    int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
    if(ret != 0) {
        LOGE("打开文件失败");
        return;
    }
    // 输出音频文件的信息
    av_dump_format(avFormatContext, 0, src_path, 0);
   // 获取音频文件的流信息
    ret = avformat_find_stream_info(avFormatContext, NULL);
    if(ret < 0) {
        LOGE("获取流信息失败");
        return;
    }
    // 查找音频流在文件的所有流集合中的位置
    int streamIndex = 0;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
        if(avMediaType == AVMEDIA_TYPE_AUDIO) {  //这边和视频不一样,是AUDIO
            streamIndex = i;
        }
    }
    // 拿到对应音频流的参数
    AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
    // 获取解码器的标识ID
    enum AVCodecID avCodecId = avCodecParameters->codec_id;
    // 通过获取的ID,获取对应的解码器
    AVCodec *avCodec = avcodec_find_decoder(avCodecId);
    // 创建一个解码器上下文对象
    AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
    if (avCodecContext == NULL) {
        //创建解码器上下文失败
        LOGE("创建解码器上下文失败");
        return;
    }
    // 将新的API中的 codecpar 转成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avCodecParameters);
    ret = avcodec_open2(avCodecContext, avCodec, NULL);
    if (ret < 0) {
        LOGE("打开解码器失败 ");
        return;
    }
    LOGE("decodec name: %s", avCodec->name);

    //压缩数据包
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //解压缩后存放的数据帧的对象
    AVFrame *inFrame = av_frame_alloc();
    //frame->16bit 44100 PCM 统一音频采样格式与采样率
    //创建swrcontext上下文件
    SwrContext *swrContext = swr_alloc();
    //音频格式  输入的采样设置参数
    AVSampleFormat inFormat = avCodecContext->sample_fmt;
    // 出入的采样格式
    AVSampleFormat  outFormat = AV_SAMPLE_FMT_S16;
    // 输入采样率
    int inSampleRate = avCodecContext->sample_rate;
    // 输出采样率
    int outSampleRate = 44100;
    // 输入声道布局
    uint64_t in_ch_layout = avCodecContext->channel_layout;
    //输出声道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //给Swrcontext 分配空间,设置公共参数
    swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
            in_ch_layout, inFormat, inSampleRate, 0, NULL
            );
    // 初始化
    swr_init(swrContext);
    // 获取声道数量
    int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);

    int currentIndex = 0;
    LOGE("声道数量%d ", outChannelCount);
    // 设置音频缓冲区间 16bit   44100  PCM数据, 双声道
    uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
    // 创建pcm的文件对象
    FILE *fp_pcm = fopen(dst_path, "wb");
    //开始读取源文件,进行解码
    while (av_read_frame(avFormatContext, packet) >= 0) {
        if (packet->stream_index == streamIndex) {
            avcodec_send_packet(avCodecContext, packet);
            //解码
            ret = avcodec_receive_frame(avCodecContext, inFrame);
            if(ret == 0) {
                //将每一帧数据转换成pcm
                swr_convert(swrContext, &out_buffer, 2 * 44100,
                            (const uint8_t **) inFrame->data, inFrame->nb_samples);
                //获取实际的缓存大小
                int out_buffer_size=av_samples_get_buffer_size(NULL, outChannelCount, inFrame->nb_samples, outFormat, 1);
                // 写入文件
                fwrite(out_buffer, 1, out_buffer_size,fp_pcm);
            }
            LOGE("正在解码%d", currentIndex++);
        }
    }
    // 及时释放
    fclose(fp_pcm);
    av_frame_free(&inFrame);
    av_free(out_buffer);
    swr_free(&swrContext);
    avcodec_close(avCodecContext);
    avformat_close_input(&avFormatContext);

    env->ReleaseStringUTFChars(src_audio_path, src_path);
    env->ReleaseStringUTFChars(dst_audio_path, dst_path);

利用FFmpeg和原生的AudioTrack 进行播放

思路:由FFmpeg进行解码,将解码后的数据再通过jni传到Java中的audioTrack对象进行播放

  • 创建AudioTrack对象
public class AudioPlayer {

  private AudioTrack audioTrack;

  public AudioPlayer() {

  }

  public void play(final String audioPath) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        nativePlay(audioPath);
      }
    }).start();

  }

  public native void nativePlay(String audioPath);

  /**
   * 这个方法是给C++ 调用的, 在ffmpeg获取的音频频率和通道数来调用原生的openSl的音频播放
   *
   * @param sampleRate   音频文件的频率
   * @param channelCount 通道数
   */
  public void createAudio(int sampleRate, int channelCount) {
     //通过通道数来判断是单声道还是立体声
    int channelConfig;
    if (channelCount == 1) {
      channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    } else if (channelCount == 2) {
      channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
    } else {
      channelConfig = AudioFormat.CHANNEL_OUT_MONO;
    }

    //获取实际的缓存大小
    int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig,
        AudioFormat.ENCODING_PCM_16BIT);
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, channelConfig,
        AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
    audioTrack.play();
  }

  public synchronized void playTrack(byte[] buffer, int length) {
    if(audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
      //将ffmpeg解析出来而定音频数据,写入到open es中
      audioTrack.write(buffer, 0, length);
    }
  }
}
  • FFmpeg的解码
const char* src_path = env->GetStringUTFChars(audio_path, NULL);

    AVFormatContext *avFormatContext = avformat_alloc_context();
    int ret = avformat_open_input(&avFormatContext, src_path, NULL, NULL);
    if(ret != 0) {
        LOGE("打开文件失败");
        return;
    }

    av_dump_format(avFormatContext, 0, src_path, 0);

    ret = avformat_find_stream_info(avFormatContext, NULL);
    if(ret < 0) {
        LOGE("获取流信息失败");
        return;
    }

    int streamIndex = 0;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        enum AVMediaType avMediaType = avFormatContext->streams[i]->codecpar->codec_type;
        if(avMediaType == AVMEDIA_TYPE_AUDIO) {
            streamIndex = i;
        }
    }

    AVCodecParameters *avCodecParameters = avFormatContext->streams[streamIndex]->codecpar;
    enum AVCodecID avCodecId = avCodecParameters->codec_id;

    AVCodec *avCodec = avcodec_find_decoder(avCodecId);

    AVCodecContext *avCodecContext = avcodec_alloc_context3(NULL);
    if (avCodecContext == NULL) {
        //创建解码器上下文失败
        LOGE("创建解码器上下文失败");
        return;
    }
    // 将新的API中的 codecpar 转成 AVCodecContext
    avcodec_parameters_to_context(avCodecContext, avCodecParameters);
    ret = avcodec_open2(avCodecContext, avCodec, NULL);
    if (ret < 0) {
        LOGE("打开解码器失败 ");
        return;
    }
    LOGE("decodec name: %s", avCodec->name);

    //压缩数据
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //解压缩数据
    AVFrame *inFrame = av_frame_alloc();
    //frame->16bit 44100 PCM 统一音频采样格式与采样率
    //创建swrcontext上下文件
    SwrContext *swrContext = swr_alloc();
    //音频格式  输入的采样设置参数
    AVSampleFormat inFormat = avCodecContext->sample_fmt;
    // 出入的采样格式
    AVSampleFormat  outFormat = AV_SAMPLE_FMT_S16;
    // 输入采样率
    int inSampleRate = avCodecContext->sample_rate;
    // 输出采样率
    int outSampleRate = 44100;
    // 输入声道布局
    uint64_t in_ch_layout = avCodecContext->channel_layout;
    //输出声道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
    //给Swrcontext 分配空间,设置公共参数
    //struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
    //                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
    //                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
    //                                      int log_offset, void *log_ctx);
    swr_alloc_set_opts(swrContext, out_ch_layout, outFormat, outSampleRate,
                       in_ch_layout, inFormat, inSampleRate, 0, NULL
    );
    // 初始化
    swr_init(swrContext);
    // 获取声道数量
    int outChannelCount = av_get_channel_layout_nb_channels(out_ch_layout);
    //获取native方法所在的Java的类
    jclass jclz = env->GetObjectClass(instance);
    //获取方法createAudio的id
    jmethodID create_method_id = env->GetMethodID(jclz, "createAudio", "(II)V");
    // 调用createAudio的Java方法
    env->CallVoidMethod(instance, create_method_id, outSampleRate, outChannelCount);
    // 获取方法playTrack的id
    jmethodID play_method_id = env->GetMethodID(jclz, "playTrack", "([BI)V");
    // 设置音频缓冲区间 16bit   44100  PCM数据, 双声道
    uint8_t *out_buffer = (uint8_t *) av_malloc(2 * 44100);
    int frameCount = 0;
    while (av_read_frame(avFormatContext, packet) >= 0) {
        if (packet->stream_index == streamIndex) {
            avcodec_send_packet(avCodecContext, packet);
            //解码
            ret = avcodec_receive_frame(avCodecContext, inFrame);
            if (ret == 0) {
                frameCount ++;
                LOGE("解码 %d",frameCount);
                /**
                 * int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);
                 */
                swr_convert(swrContext, &out_buffer, 44100 * 2, (const uint8_t **) inFrame->data, inFrame->nb_samples);
//                缓冲区的大小
                int size = av_samples_get_buffer_size(NULL, outChannelCount, inFrame->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);
                // 调用将数据写入到audiotrack,进行播放
                env->CallVoidMethod(instance, play_method_id, audio_sample_array, size);
                env->DeleteLocalRef(audio_sample_array);

            }
        }
    }

结语

以上就是利用FFmpeg对音频文件进行解码以及播放的内容,如果有错误,欢迎大家指正出来

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • SpringMVC的笔记

    使用Spring MVC,配置DispatcherServlet是第一步。 DispatcherServlet是一个Servlet,所以可以配置多个Dispa...

    包子388321
  • FFmpeg 内容介绍 音视频解码和播放

    FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案...

    包子388321
  • C++中 include<>和""的区别

    #include<>直接从编译器自带的函数库中寻找文件 或者说是系统目录、Path变量设置的目录开始寻找

    包子388321
  • 目标检测算法综述 | 基于候选区域的目标检测器 | CV | 机器视觉

    自从 AlexNet 获得 ILSVRC 2012 挑战赛冠军后,用 CNN 进行分类成为主流。一种用于目标检测的暴力方法是从左到右、从上到下滑...

    用户7623498
  • 带你深入理解Python属性查找

    今天扣丁学堂给大家介绍一下关于python视频教程中的属性查找,首先在Python中属性查找(attributelookup)是比较复杂的,特别是涉及到描述符d...

    企鹅号小编
  • HTML5学习-day01【悟空教程】

    网页超文本应用技术工作小组是一个以推动网络HTML 5 标准为目的而成立的组织。在2004年,由Opera、Mozilla基金会和苹果这些浏览器厂商组成。

    Java帮帮
  • 移动Web开发(一)

    a.程序访问: ECMAScript(ES) 3 、 ES 5 、 ES hamony 、 Web IDL 、DOM 2\3 、Offline 、File ...

    从今若
  • 吴章金: 深度剖析 Linux共享库的“位置无关”实现原理

    共享库有一个很重要的特征,就是可以被多个可执行文件共享,以达到节省磁盘和内存空间的目标:

    Linux阅码场
  • java进阶|MySQL数据库系列(一)数据库操作和建表操作

    后端Coder
  • PWN:House Of Einherjar

    参考:http://blog.topsec.com.cn/pwn的艺术浅谈(二):linux堆相关/

    yichen

扫码关注云+社区

领取腾讯云代金券