??今天的主題是音頻解碼,主要實(shí)現(xiàn)將視頻中的音頻解碼為音頻采用格式pcm。
首先在文章開頭給出兩個(gè)問(wèn)題
第一個(gè)問(wèn)題:音頻解碼和視頻解碼目的是什么萍肆?為什么要進(jìn)行音頻壓縮或者是視頻壓縮卜壕?
目的:壓縮音頻流、視頻流蛔六、字幕流等等…(減小數(shù)據(jù)量)
第二個(gè)問(wèn)題:音頻采樣數(shù)據(jù)作用?
保存音頻中每一個(gè)采樣點(diǎn)的值
我們來(lái)計(jì)算2分鐘pcm音頻的大小:
規(guī)定:采樣率:44100HZ
在圖像學(xué)中:每8位 = 1字節(jié)
編碼(采樣精度):16位 = 2字節(jié)
聲道數(shù)量:2個(gè)
pcm格式體積 = 2 * 60 * 44100 * 2 * 2 = 21MB
mp3 = 2MB
從計(jì)算過(guò)程就可以看出废亭,實(shí)際情況必須采用壓縮來(lái)存儲(chǔ)音頻国章。
那么pcm有哪些格式,我們平常說(shuō)的單聲道豆村,雙聲道又是什么回事液兽?這里又分兩種情況
第一種:單聲道(左右聲道)
第二種:雙聲道(排版順序"左右","左右")
二者都是采樣點(diǎn)順序排版存儲(chǔ)
那么音頻解碼的環(huán)節(jié)又是怎樣的過(guò)程呢,參照官方給出的流程圖
從上圖我們可以看出音頻解碼流程
第一步:注冊(cè)所有的組件(編解碼掌动、濾鏡特效處理庫(kù)四啰、封裝格式處理庫(kù)、工具庫(kù)粗恢、音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)柑晒、視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換等等...)
第二步:獲取音頻封裝格式信息
第三步:查找音頻流
第四步:查找音頻解碼器
第五步:打開音頻解碼器
第六步:讀取音頻壓縮數(shù)據(jù)進(jìn)行解碼(循環(huán)解碼)
第七步:關(guān)閉音頻解碼器釋放內(nèi)存
源碼
#include <jni.h>
#include <string>
//導(dǎo)入android-log日志
#include <android/log.h>
//當(dāng)前C++兼容C語(yǔ)言
extern "C"{
//avcodec:編解碼(最重要的庫(kù))
#include "libavcodec/avcodec.h"
//avformat:封裝格式處理
#include "libavformat/avformat.h"
//avutil:工具庫(kù)(大部分庫(kù)都需要這個(gè)庫(kù)的支持)
#include "libavutil/imgutils.h"
//swscale:視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換
#include "libswscale/swscale.h"
//導(dǎo)入音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)
#include "libswresample/swresample.h"
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegTest
(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoder
(JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath);
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoderAudio
(JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath);
}
//1、NDK音視頻編解碼:FFmpeg-測(cè)試配置
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegTest(
JNIEnv *env, jobject jobj) {
//(char *)表示C語(yǔ)言字符串
const char *configuration = avcodec_configuration();
__android_log_print(ANDROID_LOG_INFO,"main","%s",configuration);
}
//NDK音視頻編解碼:FFmpeg-音頻解碼-音頻采樣數(shù)據(jù)pcm格式
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoderAudio
(JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath) {
//第一步:注冊(cè)所有的組件
// (編解碼眷射、濾鏡特效處理庫(kù)敦迄、封裝格式處理庫(kù)、工具庫(kù)凭迹、音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)罚屋、視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換等等...)
av_register_all();
avcodec_register_all();
//第二步:獲取音頻封裝格式信息
AVFormatContext *avformat_context = avformat_alloc_context();
//參數(shù)一:封裝格式上下文->保存了音頻信息
//參數(shù)二:輸入文件(你要對(duì)那一個(gè)文件進(jìn)行解封裝)
//普及知識(shí):env是JNI環(huán)境指針(作用:專門用于管理對(duì)象創(chuàng)建和銷毀)
const char *cInFilePath = env->GetStringUTFChars(jInFilePath, NULL);
//參數(shù)三:封裝格式類型(NULL:表示系統(tǒng)自動(dòng)獲取格式類型)
//返回值:avformat_open_input_result = 0表示成功,否則失敗
int avformat_open_input_result = avformat_open_input(&avformat_context, cInFilePath, NULL,
NULL);
if (avformat_open_input_result != 0) {
char *error_info;
av_strerror(avformat_open_input_result, error_info, 1024);
__android_log_print(ANDROID_LOG_INFO, "main", "獲取失敗嗅绸,錯(cuò)誤信息:%s", error_info);
return;
}
//第三步:查找音頻流
//返回值:>=0 if OK, AVERROR_xxx on error(>=0表示成功脾猛,否則失敗)
int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
if (avformat_find_stream_info_result < 0) {
char *error_info;
av_strerror(avformat_find_stream_info_result, error_info, 1024);
__android_log_print(ANDROID_LOG_INFO, "main", "查找失敗,錯(cuò)誤信息:%s", error_info);
return;
}
//第四步:查找音頻解碼器
//第一點(diǎn):查找音頻流索引位置
int av_stream_index_audio = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
if (avformat_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
av_stream_index_audio = i;
break;
}
}
if (av_stream_index_audio == -1) {
__android_log_print(ANDROID_LOG_INFO, "main", "沒(méi)有找到音頻流");
return;
}
//第二點(diǎn):查找音頻解碼器上下文(根據(jù)流索引位置->獲取音頻解碼買上下文)
//新的API
// AVCodecParameters *avcodec_parameters = avformat_context->streams[av_stream_index_audio]->codecpar;
// avcodec_parameters->codec_id;
//老的API
AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index_audio]->codec;
//第三點(diǎn):根據(jù)音頻解碼器上下文->獲取到->音頻解碼器
AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (avcodec == NULL) {
__android_log_print(ANDROID_LOG_INFO, "main", "找不到這個(gè)音頻解碼器");
return;
}
//第五步:打開音頻解碼器(調(diào)試運(yùn)行->觀察鎖定C/C++基于NDK開發(fā)異常->便于調(diào)試運(yùn)行)
int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
if (avcodec_open2_result != 0) {
char *error_info;
av_strerror(avcodec_open2_result, error_info, 1024);
__android_log_print(ANDROID_LOG_INFO, "main", "打開音頻解碼器失敗鱼鸠,錯(cuò)誤信息:%s", error_info);
return;
}
//打印音頻信息
//輸出視頻信息
//輸出:文件格式
__android_log_print(ANDROID_LOG_INFO, "main", "文件格式:%s", avformat_context->iformat->name);
//輸出:解碼器名稱
__android_log_print(ANDROID_LOG_INFO, "main", "解碼器名稱:%s", avcodec->name);
//第六步:讀取音頻壓縮數(shù)據(jù)進(jìn)行解碼(循環(huán)解碼)
//讀取一幀音頻壓縮數(shù)據(jù)(緩沖區(qū))
AVPacket *av_packet = (AVPacket *) av_malloc(sizeof(AVPacket));
//接收一幀音頻采樣數(shù)據(jù)(緩沖區(qū))
AVFrame *av_frame_in = av_frame_alloc();
//音頻解碼返回結(jié)果
int avcodec_receive_frame_result;
//音頻采樣數(shù)據(jù)上下文->SwrContext
//第一點(diǎn):創(chuàng)建上下文->開辟內(nèi)存空間(聲明)
SwrContext *swr_context = swr_alloc();
//第二點(diǎn):給我們的音頻采樣數(shù)據(jù)上下文->綁定數(shù)據(jù)
//參數(shù)一:音頻采樣數(shù)據(jù)上下文
//參數(shù)二:輸出聲道布局類型(立體聲猛拴、環(huán)繞羹铅、室內(nèi)等等...)
//立體聲
int out_ch_layout = AV_CH_LAYOUT_STEREO;
//參數(shù)三:輸出音頻采樣數(shù)據(jù)格式(說(shuō)白了:采樣精度)
AVSampleFormat av_sample_format = AV_SAMPLE_FMT_S16;
//參數(shù)四:輸出音頻采樣數(shù)據(jù)->采樣率
int out_sample_rate = avcodec_context->sample_rate;
//參數(shù)五:輸入聲道布局類型(立體聲、環(huán)繞愉昆、室內(nèi)等等...)->默認(rèn)格式
int in_ch_layout = av_get_default_channel_layout(avcodec_context->channels);
//參數(shù)六:輸入音頻采樣數(shù)據(jù)格式(說(shuō)白了:采樣精度)
AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
//參數(shù)七:輸入音頻采樣數(shù)據(jù)->采樣率
int in_sample_rate = avcodec_context->sample_rate;
//參數(shù)八:Log日志偏移量
//參數(shù)九:Log日志統(tǒng)計(jì)上下文
swr_alloc_set_opts(swr_context,
out_ch_layout,
av_sample_format,
out_sample_rate,
in_ch_layout,
in_sample_fmt,
in_sample_rate,
0, NULL);
//輸出音頻采樣數(shù)據(jù)緩沖區(qū)(目標(biāo))->人的耳朵最大采樣率->44100HZ
int out_count = 16000;
uint8_t *out_buffer = (uint8_t *) av_malloc(out_count);
int pktsize, flush_complete = 0;
//獲取聲道數(shù)量
int out_nb_layout = av_get_channel_layout_nb_channels(out_ch_layout);
//打開文件
const char *coutputFilePath = env->GetStringUTFChars(jOutFilePath, NULL);
FILE *out_file_pcm = fopen(coutputFilePath, "wb+");
if (out_file_pcm == NULL) {
__android_log_print(ANDROID_LOG_INFO, "main", "文件不存在");
return;
}
int frame_index = 0;
// while (av_read_frame(avformat_context, av_packet) == 0) {
// ++frame_index;
// if (av_packet->stream_index == av_stream_index_audio) {
// out_buffer = av_packet->data;
// pktsize = av_packet->size;
// int frameFinished = 0;
// int len = avcodec_decode_audio4(avcodec_context, av_frame_in, &frameFinished,
// av_packet);
// if (frameFinished) {
// pktsize -= len;
// out_buffer += len;
// int data_size = av_samples_get_buffer_size(NULL,
// out_nb_layout,
// av_frame_in->nb_samples,
// av_sample_format,
// 1);
// /*****************************************************
// 以下代碼使用swr_convert函數(shù)進(jìn)行轉(zhuǎn)換职员,但是轉(zhuǎn)換后的文件連mp3到pcm文件都不能播放了,所以注釋了
// const uint8_t *in[] = {frame->data[0]};
//
// int len=swr_convert(swrContext,out,sizeof(audio_buf)/codecContext->channels/av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P),
// in,frame->linesize[0]/codecContext->channels/av_get_bytes_per_sample(codecContext->sample_fmt));
//
// len=len*codecContext->channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P);
//
// if (len < 0) {
// fprintf(stderr, "audio_resample() failed\n");
// break;
// }
// if (len == sizeof(audio_buf) / codecContext->channels / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P)) {
// fprintf(stderr, "warning: audio buffer is probably too small\n");
// swr_init(swrContext);
// }
// *****************************************************/
// char *data = (char *) malloc(data_size);
// short *sample_buffer = (short *) av_frame_in->data[0];
// for (int i = 0; i < data_size / 2; i++) {
// data[i * 2] = (char) (sample_buffer[i / 2] & 0xFF);
// data[i * 2 + 1] = (char) ((sample_buffer[i / 2] >> 8) & 0xFF);
//
// }
// fwrite(data, data_size, 1, out_file_pcm);
// fflush(out_file_pcm);
// }
// }
//返回值:<0表示讀取完畢跛溉,否則正在讀取
while (av_read_frame(avformat_context, av_packet) >= 0) {
//判定當(dāng)前幀是否是音頻流->音頻采樣數(shù)據(jù)
if (av_packet->stream_index == av_stream_index_audio) {
//確定是我們的音頻流->解碼
//解碼一幀音頻流數(shù)據(jù)
//老的API
//avcodec_decode_audio4();
//新的API(發(fā)送->接收)
//發(fā)送
avcodec_send_packet(avcodec_context, av_packet);
//接收
avcodec_receive_frame_result = avcodec_receive_frame(avcodec_context, av_frame_in);
if (avcodec_receive_frame_result == 0) {
//解碼一幀音頻壓縮數(shù)據(jù)成功->得到了->一幀音頻采樣數(shù)據(jù)
//音頻采樣數(shù)據(jù)->轉(zhuǎn)成pcm格式
//將輸入->輸出(pcm格式)
//參數(shù)一:音頻采樣數(shù)據(jù)上下文->SwrContext
//參數(shù)二:輸出音頻采樣數(shù)據(jù)緩沖區(qū)(目標(biāo))
//參數(shù)三:輸出緩沖區(qū)大小
//參數(shù)四:輸入音頻采樣數(shù)據(jù)緩沖區(qū)
//參數(shù)五:輸入緩沖區(qū)大小
swr_convert(swr_context,
&out_buffer,
out_count,
(const uint8_t **) av_frame_in->data,
av_frame_in->nb_samples);
//獲取緩沖區(qū)實(shí)際數(shù)據(jù)大小
//參數(shù)一:行大小
//參數(shù)二:聲道數(shù)量
//參數(shù)三:輸出大小
//參數(shù)四:輸出音頻采樣數(shù)據(jù)格式
//參數(shù)五:字節(jié)對(duì)齊類型
int out_buffer_size = av_samples_get_buffer_size(NULL,
out_nb_layout,
av_frame_in->nb_samples,
av_sample_format,
1);
//寫入文件
fwrite(av_frame_in->data[0], 1, out_buffer_size, out_file_pcm);
fflush(out_file_pcm);
frame_index++;
__android_log_print(ANDROID_LOG_INFO, "main", "當(dāng)前是第%d幀", frame_index);
__android_log_print(ANDROID_LOG_INFO, "main", "當(dāng)前是第%d幀", out_buffer);
}
}
}
//第七步:關(guān)閉音頻解碼器釋放內(nèi)存
av_packet_free(&av_packet);
//關(guān)閉流
fclose(out_file_pcm);
swr_free(&swr_context);
av_free(out_buffer);
avcodec_close(avcodec_context);
avformat_free_context(avformat_context);
}
是不是和視頻解碼大體流程完全一樣焊切,唯一的區(qū)別就是視頻像素?cái)?shù)據(jù)格式的轉(zhuǎn)換和音頻采樣數(shù)據(jù)的重新采樣了。
上述代碼實(shí)現(xiàn)完成之后可以在pc端下載Adobe audition cs6來(lái)直接播放