FFmpeg解码音频
2017-06-10 16:59
141 查看
基于第一篇文章:
第一篇解码视频
我们知道mp4是视频格式,其实内部封装了音频的压缩数据,和视频的压缩数据,这篇文章将从视频中读取音频压缩数据,并且解压缩音频
以下图片转载自 雷霄骅博士ppt
音频的压缩格式有aac,和MP3等等.他们都是通过采样格式(pcm)转化而来
如图:
几个术语
采样率:
采样频率,也称为采样速度或者采样率,定义了每秒从连续信号中提取并组成离散信号的采样个数,它用赫兹(Hz)来表示。采样频率的倒数是采样周期或者叫作采样时间,它是采样之间的时间间隔。通俗的讲采样频率是指计算机每秒钟采集多少个信号样本。一般为44100hz
比特率:
声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标。 视频中的比特率(码率)原理与声音中的相同,都是指由模拟信号转换为数字信号后,单位时间内的二进制数据量。
声道数目:
一般一个声音有多个声轨,最终将声轨混合成最后的音效
具体代码:
上面的代码比较简单:传入一个视频地址,和一个输出解压后视频格式地址即可(解压后的视频文件,可以用Adobe audio播放)
native实现文件:
Android.mk文件
native实现类
第一篇解码视频
我们知道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); }
相关文章推荐
- ffmpeg解码音频的两种方式(一)av_parser_parse解析器
- ffmpeg中音频解码方法(附代码)+ffmpeg音频解码播放速度快的问题(随手笔记,以供查阅)
- FFMPEG视音频编解码零基础学习方法
- [总结]FFMPEG视音频编解码零基础学习方法
- iOS 音视频高级编程:Audio Unit播放FFmpeg解码的音频
- FFmpeg 视频、音频编解码的例子
- ffmpeg库音频解码示例
- FFMPEG视音频编解码零基础学习方法
- FFMPEG视音频编解码零基础学习方法
- ffmpeg库音频解码示例
- [总结]FFMPEG视音频编解码零基础学习方法
- [置顶] [总结]FFMPEG视音频编解码零基础学习方法
- iOS 音视频开发:Audio Unit播放FFmpeg解码的音频
- FFmpeg - 音频解码过程
- [总结]FFMPEG视音频编解码零基础学习方法
- [转载] FFMPEG视音频编解码零基础学习方法
- ffmpeg解码音频的两种方式(二)根据同步字节解析音频帧
- FFMPEG视音频编解码零基础学习方法-b
- [总结]FFMPEG视音频编解码零基础学习方法
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)