FFmpeg解码音频
程序员文章站
2022-07-13 13:31:24
...
基于第一篇文章:
第一篇解码视频
我们知道mp4是视频格式,其实内部封装了音频的压缩数据,和视频的压缩数据,这篇文章将从视频中读取音频压缩数据,并且解压缩音频
以下图片转载自 雷霄骅博士ppt
音频的压缩格式有aac,和MP3等等.他们都是通过采样格式(pcm)转化而来
如图:
几个术语
采样率:
采样频率,也称为采样速度或者采样率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。采样频率的倒数是采样周期或者叫作采样时间,它是采样之间的时间间隔。通俗的讲采样频率是指计算机每秒钟采集多少个信号样本。一般为44100hz
比特率:
声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标。 视频中的比特率(码率)原理与声音中的相同,都是指由模拟信号转换为数字信号后,单位时间内的二进制数据量。
声道数目:
一般一个声音有多个声轨,最终将声轨混合成最后的音效
PCM格式介绍
具体代码:
//MainActivity.java
package com.fmy.demoffmepegaudio;
import java.io.File;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
static{
System.loadLibrary("DemoFFmepegAudio");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final File inputFile = new File(Environment.getExternalStorageDirectory(),"a.mov");
final File outFile = new File(Environment.getExternalStorageDirectory(),"b.pcm");
//由于是耗时操作 所以开了个线程
new Thread(){
public void run() {
ffmpegAudio(inputFile.getAbsolutePath(), outFile.getAbsolutePath());
};
}.start();
}
/**
*
* @param input 要传入的音频路径
* @param outString 解压缩后pcm路径
*/
public static native void ffmpegAudio(String input,String outString);
}
上面的代码比较简单:传入一个视频地址,和一个输出解压后视频格式地址即可(解压后的视频文件,可以用Adobe audio播放)
native实现文件:
#include <jni.h>
#include "com_fmy_demoffmepegaudio_MainActivity.h"
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//重采样
#include "libswresample/swresample.h"
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO," FMY",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FMY",FORMAT,##__VA_ARGS__);
JNIEXPORT void JNICALL Java_com_fmy_demoffmepegaudio_MainActivity_ffmpegAudio
(JNIEnv * env, jclass jcs, jstring input, jstring out){
char * input_cstr = (*env)->GetStringUTFChars(env,input,NULL);
char * output_cstr = (*env)->GetStringUTFChars(env,out,NULL);
//注册组件
av_register_all();
//封装格式
AVFormatContext *pFormatCtcx =avformat_alloc_context();
//打开输入视频文件
if(avformat_open_input(&pFormatCtcx,input_cstr,NULL,NULL)!=0){
LOGE("%s","打开输入视频文件视频");
return;
}
//获取视频信息
if(avformat_find_stream_info(pFormatCtcx,NULL)<0){
LOGE("%s","获取视频信息失败");
return;
}
//获取
int i = 0,audio_stream_idx = -1;
//获取音频流索引
for(;i<pFormatCtcx->nb_streams;++i){
if(pFormatCtcx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
//找不到音频流
if(i>=pFormatCtcx->nb_streams){
LOGE("找不到音频流");
}
//获取解码器上下文
AVCodecContext *condecCtx = pFormatCtcx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(condecCtx->codec_id);
if(codec==NULL){
LOGE("%s","无法获取解码器");
return;
}
//打开解码器
if(avcodec_open2(condecCtx,codec,NULL) < 0){
LOGI("%s","无法打开解码器");
return;
}
//压缩数据
AVPacket * packet = av_malloc(sizeof(AVPacket));
// //解压数据
AVFrame *frame = av_frame_alloc();
//frame->16bit 44100 PCM 统一音频采样格式与采样率
SwrContext *swrCtr =swr_alloc();
//输入的采样格式
enum AVSampleFormat in_sample_fmt = condecCtx->sample_fmt;
//输出采样格式16bit PCM
enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
//输入采样率
int in_sample_rate = condecCtx->sample_rate;
//输出采样率
int out_sample_rate = 44100;
//获取输入声道布局 如立体声 左声道
uint64_t in_ch_layout = condecCtx->channel_layout;
//输出声道 为立体声
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
/**
*
/**
* 分配swrContext 如果需要可以设置/重置 普通参数、
* 如果需要,分配SwrContext并设置/重置常见参数。
* Allocate SwrContext if needed and set/reset common parameters.
* 这个函数参数s可以不需要用swr_alloc()所分配的(简单说可以传入NULL),
* 另一方面 use swr_alloc_set_opts()可以在swr_alloc()所分配
* 的上下文中设置数据
*
*
* @param s 现有可用的Swr context ,如果没有那么为NULL
* @param out_ch_layout 输出通道布局 (AV_CH_LAYOUT_*)
* @param out_sample_fmt 输出采样格式 (AV_SAMPLE_FMT_*).
* @param out_sample_rate 输出采样率(frequency in Hz)
* @param in_ch_layout 输入声道布局(AV_CH_LAYOUT_*)
* @param in_sample_fmt 输入采样格式 (AV_SAMPLE_FMT_*).
* @param in_sample_rate 输入采样率(比特率) (frequency in Hz)
* @param log_offset 日志偏移
* @param log_ctx 日志上下文可以为空
*
* @see swr_init(), swr_free()
* @return 再错误的情况下返回空,否则分配上下文
*/
swr_alloc_set_opts(swrCtr,out_ch_layout,out_sample_fmt,out_sample_rate,in_ch_layout,in_sample_fmt,in_sample_rate,0,NULL);
//初始化
swr_init(swrCtr);
//输出的声道个数
int out_channel_nb= av_get_channel_layout_nb_channels(out_ch_layout);
//16bit 44100 PCM 数据
uint8_t *out_buffer = (uint8_t *)av_malloc(48000*4);
//输出
FILE *fp_pcm =fopen(output_cstr,"wb");
int got_frame = 0,index = 0 ,ret;
while(av_read_frame(pFormatCtcx,packet) >=0){
if(packet->stream_index!=audio_stream_idx){
LOGE("当前非音频流");
}
//解码
/**
*解码音频帧 avpkt->size from avpkt->data到frame中
* 一些解码器可能支持一个AVPacket存在多个帧的情况.
* 这样的解码器就只是解码第一帧,并且返回的数值小于数据包.
* 这种情况下,avcodec_decode_audio4 必须再次调用AVPacket包含剩余数据以便解码第二帧等.即使没有帧返回,
* 这个packet也需要传入到解码器中 ,直到剩余数据完全被消耗或者发送错误
* 一些解码器(那些标有CODEC_CAP_DELAY) 存在输入和输出之间的延迟.这意味着一些packets他们不会立即生成解码输出
* 并且需要在结束的解码时候刷新以便得到所有的解码数据
* 通过调用这个函数设置刷新包的avpkt->data 设置为NULL和avpkt->size 设置为 0 直到它停止返回样本.
* 它可以安全的刷新 即使那些解码器没有标记CODEC_CAP_DELAY,然后不会返回样本
*
* @warning 这个输入缓存,avpkt->data 必须FF_INPUT_BUFFER_PADDING_SIZE数据大于实际读取的字节
* 因为一些优化字节流读取者阅读32或者64位可以一次性读取到结尾
*
* @note 在数据传入解码器之前这个AVCodecContext必须已经打开@ref avcodec_open2()
*
* @param avctx 解码器上下文
* @param[out] frame 用于存储音频样本的一个frame.
* 这个解码器将调用 AVCodecContext.get_buffer2()回调为解码的帧分配一个缓冲区
* 当AVCodecContext.refcounted_frames设置为1 的时候,这个帧为引用计数返回的引用属于
* 调用者的.如果这个frame不在长时间需要那么调用者必须使用using av_frame_unref()释放它
* 如果av_frame_is_writable()返回1 那么调用者可以安全的写入数据到帧中
* 当AVCodecContext.refcounted_frame设置为0时,这个返回的引用属于解码器的并且一直有效到下次使用这个函数
* 或者直到关闭或者刷新这个解码器.这个调用则不可以写入到它中
*
* @param[out] got_frame_ptr
* 如果不能解码帧则为0.否则他不为0.注意:这个属性设置为0不代表着错误发生
*
* @param[in] avpkt
* 这个输入的AVPacket 包含着输入缓存区,至少设置 avpkt->data and avpkt->size.
* 一些解码器 也可能要求设置额外的属性
*
* @return 如果解码期间错误发生 那么返回一个负数的错误码,否则返回AVPacket中消耗的字节数
*
*
*/
ret = avcodec_decode_audio4(condecCtx,frame,&got_frame,packet);
if(got_frame>0){
LOGE("正在解码");
/** 音频转化
*
* 在in和in_count能设置为0在结束去刷新最后一些样本输出
*
* 如果输入超过输出空间则输入将会被缓存
* 你能避免这个缓存现象,提供提供输出空间超过输入.
*
* 尽可能的转转化为直接可运行的而不要需要复制
*
* @param s 分配Swr上下文 ,和参数设置
*
* @param out 输出的缓存, 在packe 是audio的情况下仅第一个需要设置
* @param out_count 每个通道 中样本输出可用空间 数量
* @param in 输入的缓存, 在packe 是audio的情况下仅第一个需要设置
* @param in_count 在一个通道中有效输入样本数
*
*
* @return 每个通道样本输出数目, 错误是一个负数
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in , int in_count);
*/
swr_convert(swrCtr,&out_buffer,48000*4,frame->data,frame->nb_samples);
/**
*
* 得到给定音频参数缓存大小
* @param[out] linesize 计算的行大小,可用为NULL
* @param nb_channels 通道数目
* @param nb_samples 一个通道内样本数量(采样率)
* @param sample_fmt 采样格式
* @param align 缓存区大小对齐(0 = 默认值,1=无对齐)
* @return 请求的缓存大小, 在失败的情况下为负数
*/
//int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
// enum AVSampleFormat sample_fmt, int align);
int out_buffer_size =av_samples_get_buffer_size(NULL, out_channel_nb,
frame->nb_samples, out_sample_fmt, 1);
fwrite(out_buffer,1,out_buffer_size,fp_pcm);
}
av_frame_unref(frame);
av_free_packet(packet);
}
fclose(fp_pcm);
//释放字符串
(*env)->ReleaseStringChars(env,input,input_cstr);
(*env)->ReleaseStringChars(env,out,output_cstr);
av_frame_free(&frame);
av_free(out_buffer);
avcodec_close(condecCtx);
swr_free(&swrCtr);
//关闭打开的文件
avformat_close_input(&pFormatCtcx);
//释放上下文
avformat_free_context(pFormatCtcx);
}
Android.mk文件
LOCAL_PATH := $(call my-dir)
#ffmpeg lib
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
#LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := DemoFFmepegAudio
LOCAL_SRC_FILES := DemoFFmepegAudio.c
LOCAL_LDLIBS+= -llog
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include/ffmpeg
include $(BUILD_SHARED_LIBRARY)
用AudioTrack播放转码的音频
基于上文
//MainActivity.java
package com.fmy.demoffmepegaudio;
import java.io.File;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore.Audio;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
public int write(byte[] audioData, int offsetInBytes, int sizeInBytes) {
return sizeInBytes;
}
//初始化播放器
public AudioTrack audioInit(int sampleRateInHz){
//音频码流 PCM 16位
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
/**
* 返回 请求成功创建一个在MODE_STREAM{@link #MODE_STREAM}模式上的AudioTrack对象所需最小缓存大小
*
* @param sampleRateInHz 以hz表达资源采样率
* @param channelConfig 描述音频通道配置
* 请看 {@link AudioFormat#CHANNEL_OUT_MONO} 和
* {@link AudioFormat#CHANNEL_OUT_STEREO}
* @param audioFormat 描述音频数据格式
*
*
* See {@link AudioFormat#ENCODING_PCM_16BIT} and
* {@link AudioFormat#ENCODING_PCM_8BIT},
* and {@link AudioFormat#ENCODING_PCM_FLOAT}.
* @return {@link #ERROR_BAD_VALUE} 如果你传递无效的参数,
* or {@link #ERROR} if unable to query for output properties,
* or the minimum buffer size expressed in bytes.
*/
int buffSize = AudioTrack.getMinBufferSize(sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioFormat);
/**
*
* @param streamType 音频流类型. See
* {@link AudioManager#STREAM_VOICE_CALL}, {@link AudioManager#STREAM_SYSTEM},
* {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_MUSIC},
* {@link AudioManager#STREAM_ALARM}, and {@link AudioManager#STREAM_NOTIFICATION}.
* @param sampleRateInHz 以hz表示的初始化资源采样率
* @param channelConfig
* 描述音频通道配置
*
* See {@link AudioFormat#CHANNEL_OUT_MONO} and
* {@link AudioFormat#CHANNEL_OUT_STEREO}
* @param audioFormat 描述音频格式 .
* See {@link AudioFormat#ENCODING_PCM_16BIT},
* {@link AudioFormat#ENCODING_PCM_8BIT},
* and {@link AudioFormat#ENCODING_PCM_FLOAT}.
* @param bufferSizeInBytes 用于播放音频数据的内部缓存总大小(以字节为单位)
*
* @param mode streaming 或者 static buffer. See {@link #MODE_STATIC} and {@link #MODE_STREAM}
* @throws java.lang.IllegalArgumentException
*/
AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRateInHz, AudioFormat.CHANNEL_OUT_STEREO, audioFormat, buffSize, AudioTrack.MODE_STREAM);
return audioTrack;
}
static{
System.loadLibrary("DemoFFmepegAudio");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final File inputFile = new File(Environment.getExternalStorageDirectory(),"a.mov");
final File outFile = new File(Environment.getExternalStorageDirectory(),"b.pcm");
//由于是耗时操作 所以开了个线程
new Thread(){
public void run() {
ffmpegAudio(inputFile.getAbsolutePath(), outFile.getAbsolutePath());
};
}.start();
}
/**
*
* @param input 要传入的音频路径
* @param outString 解压缩后pcm路径
*/
public native void ffmpegAudio(String input,String outString);
}
native实现类
#include <jni.h>
#include "com_fmy_demoffmepegaudio_MainActivity.h"
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
//重采样
#include "libswresample/swresample.h"
#include <android/log.h>
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO," FMY",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"FMY",FORMAT,##__VA_ARGS__);
JNIEXPORT void JNICALL Java_com_fmy_demoffmepegaudio_MainActivity_ffmpegAudio
(JNIEnv * env, jobject jcs, jstring input, jstring out){
char * input_cstr = (*env)->GetStringUTFChars(env,input,NULL);
char * output_cstr = (*env)->GetStringUTFChars(env,out,NULL);
//注册组件
av_register_all();
//封装格式
AVFormatContext *pFormatCtcx =avformat_alloc_context();
//打开输入视频文件
if(avformat_open_input(&pFormatCtcx,input_cstr,NULL,NULL)!=0){
LOGE("%s","打开输入视频文件视频");
return;
}
//获取视频信息
if(avformat_find_stream_info(pFormatCtcx,NULL)<0){
LOGE("%s","获取视频信息失败");
return;
}
//获取
int i = 0,audio_stream_idx = -1;
//获取音频流索引
for(;i<pFormatCtcx->nb_streams;++i){
if(pFormatCtcx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){
audio_stream_idx = i;
break;
}
}
//找不到音频流
if(i>=pFormatCtcx->nb_streams){
LOGE("找不到音频流");
}
//获取解码器上下文
AVCodecContext *condecCtx = pFormatCtcx->streams[audio_stream_idx]->codec;
AVCodec *codec = avcodec_find_decoder(condecCtx->codec_id);
if(codec==NULL){
LOGE("%s","无法获取解码器");
return;
}
//打开解码器
if(avcodec_open2(condecCtx,codec,NULL) < 0){
LOGI("%s","无法打开解码器");
return;
}
//压缩数据
AVPacket * packet = av_malloc(sizeof(AVPacket));
// //解压数据
AVFrame *frame = av_frame_alloc();
//frame->16bit 44100 PCM 统一音频采样格式与采样率
SwrContext *swrCtr =swr_alloc();
//输入的采样格式
enum AVSampleFormat in_sample_fmt = condecCtx->sample_fmt;
//输出采样格式16bit PCM
enum AVSampleFormat out_sample_fmt =AV_SAMPLE_FMT_S16;
//输入采样率
int in_sample_rate = condecCtx->sample_rate;
//输出采样率
int out_sample_rate = 44100;
//获取输入声道布局 如立体声 左声道
uint64_t in_ch_layout = condecCtx->channel_layout;
//输出声道 为立体声
uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;
/**
*
/**
* 分配swrContext 如果需要可以设置/重置 普通参数、
* 如果需要,分配SwrContext并设置/重置常见参数。
* Allocate SwrContext if needed and set/reset common parameters.
* 这个函数参数s可以不需要用swr_alloc()所分配的(简单说可以传入NULL),
* 另一方面 use swr_alloc_set_opts()可以在swr_alloc()所分配
* 的上下文中设置数据
*
*
* @param s 现有可用的Swr context ,如果没有那么为NULL
* @param out_ch_layout 输出通道布局 (AV_CH_LAYOUT_*)
* @param out_sample_fmt 输出采样格式 (AV_SAMPLE_FMT_*).
* @param out_sample_rate 输出采样率(frequency in Hz)
* @param in_ch_layout 输入声道布局(AV_CH_LAYOUT_*)
* @param in_sample_fmt 输入采样格式 (AV_SAMPLE_FMT_*).
* @param in_sample_rate 输入采样率(比特率) (frequency in Hz)
* @param log_offset 日志偏移
* @param log_ctx 日志上下文可以为空
*
* @see swr_init(), swr_free()
* @return 再错误的情况下返回空,否则分配上下文
*/
swr_alloc_set_opts(swrCtr,out_ch_layout,out_sample_fmt,out_sample_rate,in_ch_layout,in_sample_fmt,in_sample_rate,0,NULL);
//初始化
swr_init(swrCtr);
//输出的声道个数
int out_channel_nb= av_get_channel_layout_nb_channels(out_ch_layout);
//16bit 44100 PCM 数据
uint8_t *out_buffer = (uint8_t *)av_malloc(48000*4);
//输出
FILE *fp_pcm =fopen(output_cstr,"wb");
int got_frame = 0,index = 0 ,ret;
//获取java层数据
jclass player_class =(*env)->GetObjectClass(env,jcs);
//获取方法id
//方法名
//方法签名
jmethodID create_audio_track_mid=(*env)->GetMethodID(env,player_class,"audioInit","(I)Landroid/media/AudioTrack;");
//调用java层audioInit(int sampleRateInHz)方法获取 AudioTrack
//第二个参数调用方法对象
//第三个参数 方法id
//第四个为 可以变参数传入参数
jobject audio_track=(*env)->CallObjectMethod(env,jcs,create_audio_track_mid,in_sample_rate,in_sample_rate);
//调用AudioTrack.play方法
jclass audio_track_class = (*env)->GetObjectClass(env,audio_track);
jmethodID audio_track_play_mid = (*env)->GetMethodID(env,audio_track_class,"play","()V");
(*env)->CallVoidMethod(env,audio_track,audio_track_play_mid);
//一个用于写入音频数据的方法
jmethodID audio_track_write_mid = (*env)->GetMethodID(env,audio_track_class,"write","([BII)I");
//结束获取java层数据
while(av_read_frame(pFormatCtcx,packet) >=0){
if(packet->stream_index==audio_stream_idx){
LOGE("当前音频流");
//解码
/**
*解码音频帧 avpkt->size from avpkt->data到frame中
* 一些解码器可能支持一个AVPacket存在多个帧的情况.
* 这样的解码器就只是解码第一帧,并且返回的数值小于数据包.
* 这种情况下,avcodec_decode_audio4 必须再次调用AVPacket包含剩余数据以便解码第二帧等.即使没有帧返回,
* 这个packet也需要传入到解码器中 ,直到剩余数据完全被消耗或者发送错误
* 一些解码器(那些标有CODEC_CAP_DELAY) 存在输入和输出之间的延迟.这意味着一些packets他们不会立即生成解码输出
* 并且需要在结束的解码时候刷新以便得到所有的解码数据
* 通过调用这个函数设置刷新包的avpkt->data 设置为NULL和avpkt->size 设置为 0 直到它停止返回样本.
* 它可以安全的刷新 即使那些解码器没有标记CODEC_CAP_DELAY,然后不会返回样本
*
* @warning 这个输入缓存,avpkt->data 必须FF_INPUT_BUFFER_PADDING_SIZE数据大于实际读取的字节
* 因为一些优化字节流读取者阅读32或者64位可以一次性读取到结尾
*
* @note 在数据传入解码器之前这个AVCodecContext必须已经打开@ref avcodec_open2()
*
* @param avctx 解码器上下文
* @param[out] frame 用于存储音频样本的一个frame.
* 这个解码器将调用 AVCodecContext.get_buffer2()回调为解码的帧分配一个缓冲区
* 当AVCodecContext.refcounted_frames设置为1 的时候,这个帧为引用计数返回的引用属于
* 调用者的.如果这个frame不在长时间需要那么调用者必须使用using av_frame_unref()释放它
* 如果av_frame_is_writable()返回1 那么调用者可以安全的写入数据到帧中
* 当AVCodecContext.refcounted_frame设置为0时,这个返回的引用属于解码器的并且一直有效到下次使用这个函数
* 或者直到关闭或者刷新这个解码器.这个调用则不可以写入到它中
*
* @param[out] got_frame_ptr
* 如果不能解码帧则为0.否则他不为0.注意:这个属性设置为0不代表着错误发生
*
* @param[in] avpkt
* 这个输入的AVPacket 包含着输入缓存区,至少设置 avpkt->data and avpkt->size.
* 一些解码器 也可能要求设置额外的属性
*
* @return 如果解码期间错误发生 那么返回一个负数的错误码,否则返回AVPacket中消耗的字节数
*
*
*/
ret = avcodec_decode_audio4(condecCtx,frame,&got_frame,packet);
if(got_frame>0){
LOGE("正在解码");
/** 音频转化
*
* 在in和in_count能设置为0在结束去刷新最后一些样本输出
*
* 如果输入超过输出空间则输入将会被缓存
* 你能避免这个缓存现象,提供提供输出空间超过输入.
*
* 尽可能的转转化为直接可运行的而不要需要复制
*
* @param s 分配Swr上下文 ,和参数设置
*
* @param out 输出的缓存, 在packe 是audio的情况下仅第一个需要设置
* @param out_count 每个通道 中样本输出可用空间 数量
* @param in 输入的缓存, 在packe 是audio的情况下仅第一个需要设置
* @param in_count 在一个通道中有效输入样本数
*
*
* @return 每个通道样本输出数目, 错误是一个负数
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
const uint8_t **in , int in_count);
*/
swr_convert(swrCtr,&out_buffer,48000*4,frame->data,frame->nb_samples);
/**
*
* 得到给定音频参数缓存大小
* @param[out] linesize 计算的行大小,可用为NULL
* @param nb_channels 通道数目
* @param nb_samples 一个通道内样本数量(采样率)
* @param sample_fmt 采样格式
* @param align 缓存区大小对齐(0 = 默认值,1=无对齐)
* @return 请求的缓存大小, 在失败的情况下为负数
*/
//int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples,
// enum AVSampleFormat sample_fmt, int align);
int out_buffer_size =av_samples_get_buffer_size(NULL, out_channel_nb,
frame->nb_samples, out_sample_fmt, 1);
//out_buffer缓冲区数据,转成byte数组
jbooleanArray audio_sample_array = (*env)->NewByteArray(env,out_buffer_size);
//获取数组第一个元素 转为jbyte *然后间接对其数组赋值
jbyte* sample_bytep =(*env)->GetByteArrayElements(env,audio_sample_array,NULL);
//out_buffer的数据复制到sampe_bytep
memcpy(sample_bytep,out_buffer,out_buffer_size);
//刷新数据写入
(*env)->ReleaseByteArrayElements(env,audio_sample_array,sample_bytep,0);
(*env)->CallIntMethod(env,audio_track,audio_track_write_mid,audio_sample_array,0,out_buffer_size);
LOGE("解码一帧");
//删除局部引用 因为gc不会再此循环中回收
(*env)->DeleteLocalRef(env,audio_sample_array);
fwrite(out_buffer,1,out_buffer_size,fp_pcm);
}
av_frame_unref(frame);
av_free_packet(packet);
}
}
fclose(fp_pcm);
//释放字符串
(*env)->ReleaseStringChars(env,input,input_cstr);
(*env)->ReleaseStringChars(env,out,output_cstr);
av_frame_free(&frame);
av_free(out_buffer);
avcodec_close(condecCtx);
swr_free(&swrCtr);
//关闭打开的文件
avformat_close_input(&pFormatCtcx);
//释放上下文
avformat_free_context(pFormatCtcx);
}