NDK--利用FFmpeg进行音频解码

通过之前的知识,我们能够播放一个视频文件中的视频流,那么如何播放音频流呢?

首先来了解一下关于音频的基础知识
在物理学中,声音就是一种波,我们称之为声波,声波的三要素是频率、振幅和波形,频率代表音阶的高低,振幅代表响度,波形代表音色。
数字音频:在早期声音无法捕获和保存,后面人们发明了模拟信号,将模拟信号数字化,我们称之为数字音频,在我们日常生活中,听歌,电视中的声音等都是数字音频。将模拟信号数字化的过程有3个:采样、量化和编码。
采样:首先要对模拟信号进行采样,所谓采样就是 在时间轴上对信号进行数字化。根据奈奎斯特定理(也称为采样定 理),按比声音最高频率高2倍以上的频率对声音进行采样(也称为AD 转换),,对于高质量的音频信号,其频率范围(人耳 能够听到的频率范围)是20Hz~20kHz,所以采样频率一般为 44.1kHz,这样就可以保证采样声音达到20kHz也能被数字化,从而使得经过数字化处理之后,人耳听到的声音质量不会被降低。而所谓的 44.1kHz就是代表1秒会采样44100次
NDK--利用FFmpeg进行音频解码_第1张图片
量化:量化是指在幅度轴上对信号进行数字化,比如用16比特的二进制信号来表示声音的一个采样,而16比特(一个short)所表示的 范围是[-32768,32767],共有65536个可能取值,因此最终模拟的音频信号在幅度上也分为了65536层
NDK--利用FFmpeg进行音频解码_第2张图片
编码:所谓编码,就是按照一定的格式记录采样和量化后的数字数据,比如顺序存储或压缩存储,等等。
通常所说的音频的裸数据格式就是脉冲编码调制(Pulse Code Modulation,PCM)数据。描述一段PCM数据一 般需要以下几个概念:量化格式(sampleFormat)、采样率 (sampleRate)、声道数(channel)。以CD的音质为例:量化格式(有的地方描述为位深度)为16比特(2字节),采样率为44100,声道数为 2,这些信息就描述了CD的音质。
而对于声音格式,还有一个概念用来描述它的大小,称为数据比特率,即1秒时间内的比特数目,它用于衡量音频数据单位时间内的容量大小。而对于CD音质的数据,比特率为多少呢?计算如下:

44100 * 16 * 2 = 1378.125kbps

那么在1分钟里,这类CD音质的数据需要占据多大的存储空间呢? 计算如下:

1378.125 * 60 / 8 / 1024 = 10.09MB

当然,如果sampleFormat更加精确(比如用4字节来描述一个采样),或者sampleRate更加密集(比如48kHz的采样率),那么所占的存储空间就会更大,同时能够描述的声音细节就会越精确。存储的这段二进制数据即表示将模拟信号转换为数字信号了,以后就可以对这段二 进制数据进行存储、播放、复制,或者进行其他任何操作。
但是PCM用于网络传输还是体积太大了,所以必须对其进行压缩编码。和视频相同,压缩算法包括有损压缩和无损压缩。根据不同的应用场景(包括存储设备、传输网络环境、播放设备 等),可以选用不同的压缩编码算法,如PCM、WAV、AAC、MP3、 Ogg等。一下介绍几种常用的编码

1.WAV编码
特点:音质非常好,大量软件都支持。
适用场合:多媒体开发的中间文件、保存音乐和音效素材。
2.MP3编码
特点:音质在128Kbit/s以上表现还不错,压缩比比较高,大量软件和硬件都支持,兼容性好。
适用场合:高比特率下对兼容性有要求的音乐欣赏。
3.AAC编码
特点:在小于128Kbit/s的码率下表现优异,并且多用于视频中的音频编码。
适用场合:128Kbit/s以下的音频编码,多用于视频中音频轨的编码。
4.Ogg编码
特点:可以用比MP3更小的码率实现比MP3更好的音质,高中低码率下均有良好的表现,兼容性不够好,流媒体特性不支持。
适用场合:语音聊天的音频消息场景。

音频的基础知识就介绍完了,下面开始实现音频解码

代码和之前也是差不多,不过之前使用的是视频转码组件,现在要换成音频转码组件
#include 
#include 
#include 
#include 
#include 

extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
//像素处理
#include "libswscale/swscale.h"
}

#define  LOG_TAG    "aruba"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

extern "C"
JNIEXPORT void JNICALL
Java_com_aruba_ffmpegapplication_DecodeActivity_decodeAudio(JNIEnv *env, jobject instance,
                                                            jstring inputFilePath_,
                                                            jstring outputFilePath_) {
    const char *inputFilePath = env->GetStringUTFChars(inputFilePath_, 0);
    const char *outputFilePath = env->GetStringUTFChars(outputFilePath_, 0);

    //注册FFmpeg中各大组件
    av_register_all();

    //打开文件
    AVFormatContext *formatContext = avformat_alloc_context();
    if (avformat_open_input(&formatContext, inputFilePath, NULL, NULL) != 0) {
        LOGE("打开失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //将文件信息填充进AVFormatContext
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        LOGE("获取文件信息失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //获取视频流的编解码器上下文
    AVCodecContext *codecContext = NULL;
    int audio_stream_idx = -1;
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {//如果是音频流
            codecContext = formatContext->streams[i]->codec;
            audio_stream_idx = i;
            break;
        }
    }

    if (codecContext == NULL) {
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //根据编解码器上下文的id获取视频流解码器
    AVCodec *codec = avcodec_find_decoder(codecContext->codec_id);
    //打开解码器
    if (avcodec_open2(codecContext, codec, NULL) < 0) {
        LOGE("解码失败");
        avformat_free_context(formatContext);
        env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
        env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
        return;
    }

    //开始读每一帧
    //存放压缩数据
    AVPacket *pkt = (AVPacket *) (av_malloc(sizeof(AVPacket)));
    av_init_packet(pkt);

    //存放解压数据
    AVFrame *picture = av_frame_alloc();

    int picture_ptr = 0;

    //音频转码组件上下文
    SwrContext *swrContext = swr_alloc();
//    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);
    //AV_CH_LAYOUT_STEREO:双声道  AV_SAMPLE_FMT_S16:量化格式 16位 codecContext->sample_rate:采样率 Hz
    swr_alloc_set_opts(swrContext, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16,
                       codecContext->sample_rate,//输出采样率和输入采样率应相同
                       codecContext->channel_layout, codecContext->sample_fmt,
                       codecContext->sample_rate, 0, NULL
    );
    swr_init(swrContext);
    
    //原音频通道数
    int channel_count = av_get_channel_layout_nb_channels(codecContext->channel_layout);
    //单通道最大存放转码数据 所占字节 = 采样率*量化格式 / 8
    int out_size = 44100 * 16 / 8;
    uint8_t *out = (uint8_t *) (av_malloc(out_size));

    //文件
    FILE *output_file = fopen(outputFilePath, "wb");

    while (av_read_frame(formatContext, pkt) == 0) {//读到每一帧的压缩数据存放在AVPacket
        if (pkt->stream_index == audio_stream_idx) {
            //解码
            avcodec_decode_audio4(codecContext, picture, &picture_ptr, pkt);

            LOGE("picture_ptr %d", picture_ptr);
            if (picture_ptr > 0) {
                //转码
                swr_convert(swrContext, &out, out_size,
                            (const uint8_t **) (picture->data), picture->nb_samples);

//                int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
//                                               enum AVSampleFormat sample_fmt, int align);
                //缓冲区真实大小
                int size = av_samples_get_buffer_size(NULL, channel_count, picture->nb_samples,
                                                      AV_SAMPLE_FMT_S16, 1);
                fwrite(out, sizeof(uint8_t), size, output_file);
            }

        }
        av_free_packet(pkt);
    }

    //关闭文件
    fclose(output_file);
    //释放资源
    av_freep(out);
    swr_free(&swrContext);
    av_frame_free(&picture);
    avcodec_close(codecContext);
    avformat_free_context(formatContext);
    env->ReleaseStringUTFChars(inputFilePath_, inputFilePath);
    env->ReleaseStringUTFChars(outputFilePath_, outputFilePath);
}
下面是转换前MP3和转换后PCM的文件对比
NDK--利用FFmpeg进行音频解码_第3张图片
项目地址:https://gitee.com/aruba/FFmpegApplication.git

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1v7genpiu1cy3

你可能感兴趣的:(NDK--利用FFmpeg进行音频解码)