写点什么

FFmpeg- 之音视频解码与音视频同步(二)

用户头像
Android架构
关注
发布于: 46 分钟前

| H.264 | MPEG/ITU-T | 2003 | 各个领域 || MPEG4 | MPEG | 2001 | 小众 || MPEG2 | MPEG | 1994 | 数字电视 || VP9 | Google | 2013 | 研发中 || VP8 | Google | 2008 | 小众 |


主要音频编码格式:


音视频解码流程


  1. 解封装格式。将输入的按照一定格式封装的音视频数据,分离成为音频流压缩编码数据和视频流压缩编码数据。

  2. 解码。将视频和音频的压缩编码数据,解码成为非压缩的视频和音频原始数据。视频压缩数据通过解码输出为像素数据,如 YUV420P 、 RGB 等;音频压缩数据通过解码输出为非压缩的音频抽样数据,如 PCM 数据。

  3. 音视频同步。同步解码出来的视频和音频数据,并将音视频数据送至系统的声卡和显卡,播放和显示出来。

FFmpeg 函数库

FFmpeg 一般有 8 个函数库,各个函数库的功能如下:


FFmpeg 音视频解码

FFmpeg 音视频解码主要流程代码描述:


1. av_register_all() //注册组件 2. avformat_alloc_context //获取封装格式上下文 3. avformat_find_stream_info //获取输入文件信息 4. avcodec_find_decoder //获取解码器 5. avcodec_open2 //打开解码器 6. avcodec_decode_video2 或 avcodec_decode_audio4 //解码音视频帧


在 AS 工程中引入 FFmpeg 8 个动态库和 libyuv (负责视频像素数据格式转换)动态库。工程的头文件目录:



工程的动态库目录:



Java 层 API :


package com.haohao.ffmpeg;


import android.media.AudioFormat;import android.media.AudioManager;import android.media.AudioTrack;import android.util.Log;import android.view.Surface;


/**


  • author: haohao

  • time: 2017/12/19

  • mail: haohaochang86@gmail.com

  • desc: AVUtils*/public class AVUtils {private static final String TAG = "AVUtils";private static AVCallback AVCallback;private static AVCallback sAVCallback;public static void registerCallback(AVCallback callback) {sAVCallback = callback;}


static {System.loadLibrary("avfilter-5");System.loadLibrary("avdevice-56");System.loadLibrary("yuv");System.loadLibrary("avutil-54");System.loadLibrary("swresample-1");System.loadLibrary("avcodec-56");System.loadLibrary("avformat-56");System.loadLibrary("swscale-3");System.loadLibrary("postproc-53");System.loadLibrary("native-lib");}/**


  • 解码视频中的视频压缩数据

  • @param input_file_path 输入的视频文件路径

  • @param output_file_path 视频压缩数据解码后输出的 YUV 文件路径*/public static native void videoDecode(String input_file_path, String output_file_path);


/**


  • 显示视频视频解码后像素数据

  • @param input 输入的视频文件路径

  • @param surface 用于显示视频视频解码后的 RGBA 像素数据*/public static native void videoRender(String input, Surface surface);


/**


  • 解码视频中的音频压缩数据

  • @param input 输入的视频文件路径

  • @param output 音频压缩数据解码后输出的 PCM 文件路径*/public static native void audioDecode(String input, String output);


/**


  • 播放视频中的音频数据

  • @param input 输入的视频文件路径*/public static native void audioPlay(String input);


/**


  • 创建一个 AudioTrack 对象,用于播放音频,在 Native 层中调用。*/public static AudioTrack createAudioTrack(int sampleRate, int num_channel) {int audioFormat = AudioFormat.ENCODING_PCM_16BIT;Log.i(TAG, "声道数:" + num_channel);int channelConfig;if (num_channel == 1) {channelConfig = android.media.AudioFormat.CHANNEL_OUT_MONO;} else if (num_channel == 2) {channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;} else {channelConfig = android.media.AudioFormat.CHANNEL_OUT_STEREO;}


int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat);


AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,sampleRate, channelConfig,audioFormat,bufferSize, AudioTrack.MODE_STREAM);return audioTrack;}


public interface AVCallback {void onFinish();}}


MySurfaceView.java


/**


  • author: haohao

  • time: 2017/12/20

  • mail: haohaochang86@gmail.com

  • desc: MySurfaceView*/public class MySurfaceView extends SurfaceView {public MySurfaceView(Context context) {super(context);}


public MySurfaceView(Context context, AttributeSet attrs) {super(context, attrs);}


public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}


private void init(){// 设置像素绘制格式为 RGBA_8888SurfaceHolder holder = getHolder();holder.setFormat(PixelFormat.RGBA_8888);}}


activity_main.xml


<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent">


<com.haohao.ffmpeg.MySurfaceViewandroid:id="@+id/my_surface_view"android:layout_width="match_parent"android:layout_height="match_parent" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:alpha="0.7"android:orientation="horizontal">


<Buttonandroid:id="@+id/video_decode_btn"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:text="视频解码" />


<Buttonandroid:id="@+id/video_render_btn"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:text="视频渲染" />


</LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:alpha="0.7"android:orientation="horizontal">


<Buttonandroid:id="@+id/audio_decode_btn"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:text="音频解码" />


<Buttonandroid:id="@+id/audio_play_btn"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:text="音频播放" />


</LinearLayout></LinearLayout></FrameLayout>


MainActivity.java


public class MainActivity extends AppCompatActivity implements View.OnClickListener, AVUtils.AVCallback {private static final String TAG = "MainActivity";private static final String BASE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separatorChar;


private String input_video_file_path = BASE_PATH


  • "input.mp4";private String output_video_file_path = BASE_PATH

  • "output.yuv";private String input_audio_file_path = BASE_PATH

  • "hello.mp3";private String output_audio_file_path = BASE_PATH

  • "hello.pcm";private String video_src = BASE_PATH

  • "ffmpeg.mp4";private Button mDecodeVideoBtn;private Button mVideoRenderBtn;private Button mAudioPlayBtn, mAudioDecodeBtn;private ProgressDialog mProgressDialog;private ExecutorService mExecutorService;private MySurfaceView mySurfaceView;


@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS}, 0);}


mDecodeVideoBtn = (Button)findViewById(R.id.video_decode_btn);mVideoRenderBtn = (Button)findViewById(R.id.video_render_btn);mAudioDecodeBtn = (Button) findViewById(R.id.audio_decode_btn);mAudioPlayBtn = (Button)findViewById(R.id.audio_play_btn);


mySurfaceView = (MySurfaceView) findViewById(R.id.my_surface_view);mDecodeVideoBtn.setOnClickListener(this);mVideoRenderBtn.setOnClickListener(this);mAudioDecodeBtn.setOnClickListener(this);mAudioPlayBtn.setOnClickListener(this);


AVUtils.registerCallback(this);mProgressDialog = new ProgressDialog(this);mProgressDialog.setCanceledOnTouchOutside(false);mExecutorService = Executors.newFixedThreadPool(2);}


@Overridepublic void onClick(View view) {int id = view.getId();switch (id) {case R.id.video_decode_btn:mProgressDialog.setMessage("正在解码...");mProgressDialog.show();mExecutorService.submit(new Runnable() {@Overridepublic void run() {AVUtils.videoDecode(input_video_file_path, output_video_file_path);}});


break;case R.id.video_render_btn:mExecutorService.submit(new Runnable() {@Overridepublic void run() {AVUtils.videoRender(input_video_file_path, mySurfaceView.getHolder().getSurface());}});break;case R.id.audio_decode_btn:mProgressDialog.setMessage("正在解码...");mProgressDialog.show();mExecutorService.submit(new Runnable() {@Overridepublic void run() {AVUtils.audioDecode(input_audio_file_path, output_audio_file_path);}});break;case R.id.audio_play_btn:mExecutorService.submit(new Runnable() {@Overridepublic void run() {AVUtils.audioPlay(input_video_file_path);}});break;}


}


@Overridepublic void onFinish() {runOnUiThread(new Runnable() {@Overridepublic void run() {if (mProgressDialog.isShowing()) {mProgressDialog.dismiss();}Toast.makeText(MainActivity.this, "解码完成", Toast.LENGTH_SHORT).show();}});}


@Overrideprotected void onDestroy() {super.onDestroy();mExecutorService.shutdown();}}


nativelib.c


#include <jni.h>#include <string.h>#include <android/log.h>#include <stdio.h>#include <libavutil/time.h>


//编码 #include "include/libavcodec/avcodec.h"//封装格式处理 #include "include/libavformat/avformat.h"//像素处理 #include "include/libswscale/swscale.h"


#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"haohao",FORMAT,##VA_ARGS);#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"haohao",FORMAT,##VA_ARGS);


//中文字符串转换 jstring charsToUTF8String(JNIEnv *env, char *s) {jclass string_cls = (*env)->FindClass(env, "java/lang/String");jmethodID mid = (*env)->GetMethodID(env, string_cls, "<init>", "([BLjava/lang/String;)V");


jbyteArray jb_arr = (*env)->NewByteArray(env, strlen(s));(*env)->SetByteArrayRegion(env, jb_arr, 0, strlen(s), s);


jstring charset = (*env)->NewStringUTF(env, "UTF-8");


return (*env)->NewObject(env, string_cls, mid, jb_arr, charset);}


JNIEXPORT void JNICALLJava_com_haohao_ffmpeg_AVUtils_videoDecode(JNIEnv *env, jclass type, jstring input_,jstring output_) {


//访问静态方法 jmethodID mid = (*env)->GetStaticMethodID(env, type, "onNativeCallback", "()V");//需要转码的视频文件(输入的视频文件)const char *input = (*env)->GetStringUTFChars(env, input_, 0);const char *output = (*env)->GetStringUTFChars(env, output_, 0);


//注册所有组件 av_register_all();


//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息 AVFormatContext *pFormatCtx = avformat_alloc_context();


//打开输入视频文件 if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {LOGE("%s", "无法打开输入视频文件");return;}


//获取视频文件信息,例如得到视频的宽高 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGE("%s", "无法获取视频文件信息");return;}


//获取视频流的索引位置//遍历所有类型的流(音频流、视频流、字幕流),找到视频流 int v_stream_idx = -1;int i = 0;


for (; i < pFormatCtx->nb_streams; i++) {//判断视频流 if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {v_stream_idx = i;break;}}


if (v_stream_idx == -1) {LOGE("%s", "找不到视频流\n");return;}


//根据视频的编码方式,获取对应的解码器 AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;


//根据编解码上下文中的编码 id 查找对应的解码器 AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);


if (pCodec == NULL) {LOGE("%s", "找不到解码器,或者视频已加密\n");return;}


//打开解码器,解码器有问题(比如说我们编译 FFmpeg 的时候没有编译对应类型的解码器)if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {LOGE("%s", "解码器无法打开\n");return;}


//输出视频信息 LOGI("视频的文件格式:%s", pFormatCtx->iformat->name);LOGI("视频时长:%lld", (pFormatCtx->duration) / (1000 * 1000));LOGI("视频的宽高:%d,%d", pCodecCtx->width, pCodecCtx->height);LOGI("解码器的名称:%s", pCodec->name);


//准备读取//AVPacket 用于存储一帧一帧的压缩数据(H264)//缓冲区,开辟空间 AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));


//AVFrame 用于存储解码后的像素数据(YUV)//内存分配 AVFrame *pFrame = av_frame_alloc();//YUV420AVFrame *pFrameYUV = av_frame_alloc();//只有指定了 AVFrame 的像素格式、画面大小才能真正分配内存//缓冲区分配内存 uint8_t *out_buffer = (uint8_t *) av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));//初始化缓冲区 avpicture_fill((AVPicture *) pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width,pCodecCtx->height);


//用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等 struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,pCodecCtx->width, pCodecCtx->height,AV_PIX_FMT_YUV420P,SWS_BICUBIC, NULL, NULL, NULL);int got_picture, ret;


//输出文件 FILE *fp_yuv = fopen(output, "wb+");


int frame_count = 0;


//一帧一帧的读取压缩数据 while (av_read_frame(pFormatCtx, packet) >= 0) {//只要视频压缩数据(根据流的索引位置判断)if (packet->stream_index == v_stream_idx) {//解码一帧视频压缩数据,得到视频像素数据 ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);if (ret < 0) {LOGE("%s", "解码错误");return;}


//为 0 说明解码完成,非 0 正在解码 if (got_picture) {//AVFrame 转为像素格式 YUV420,宽高//2 6 输入、输出数据//3 7 输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的//4 输入数据第一列要转码的位置 从 0 开始//5 输入画面的高度 sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height,pFrameYUV->data, pFrameYUV->linesize);


//输出到 YUV 文件//AVFrame 像素帧写入文件//data 解码后的图像像素数据(音频采样数据)//Y 亮度 UV 色度(压缩了) 人对亮度更加敏感//U V 个数是 Y 的 1/4int y_size = pCodecCtx->width * pCodecCtx->height;fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);


frame_count++;LOGI("解码第 %d 帧", frame_count);}}


//释放资源 av_free_packet(packet);}


fclose(fp_yuv);


av_frame_free(&pFrame);


avcodec_close(pCodecCtx);


avformat_free_context(pFormatCtx);


(*env)->ReleaseStringUTFChars(env, input_, input);(*env)->ReleaseStringUTFChars(env, output_, output);//通知 Java 层解码完毕(*env)->CallStaticVoidMethod(env, type, mid);}


//使用这两个 Window 相关的头文件需要在 CMake 脚本中引入 android 库 #include <android/native_window_jni.h>#include <android/native_window.h>#include "include/yuv/libyuv.h"


JNIEXPORT void JNICALLJava_com_haohao_ffmpeg_AVUtils_videoRender(JNIEnv *env, jclass type,


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


jstring input_,jobject surface) {//需要转码的视频文件(输入的视频文件)const char *input = (*env)->GetStringUTFChars(env, input_, 0);


//注册所有组件 av_register_all();//avcodec_register_all();


//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息 AVFormatContext *pFormatCtx = avformat_alloc_context();


//打开输入视频文件 if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {LOGE("%s", "无法打开输入视频文件");return;}


//获取视频文件信息,例如得到视频的宽高//第二个参数是一个字典,表示你需要获取什么信息,比如视频的元数据 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGE("%s", "无法获取视频文件信息");return;}


//获取视频流的索引位置//遍历所有类型的流(音频流、视频流、字幕流),找到视频流 int v_stream_idx = -1;int i = 0;//number of streamsfor (; i < pFormatCtx->nb_streams; i++) {//流的类型 if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {v_stream_idx = i;break;}}


if (v_stream_idx == -1) {LOGE("%s", "找不到视频流\n");return;}


//获取视频流中的编解码上下文 AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;


//根据编解码上下文中的编码 id 查找对应的解码器 AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);


if (pCodec == NULL) {LOGE("%s", "找不到解码器,或者视频已加密\n");return;}


//打开解码器,解码器有问题(比如说我们编译 FFmpeg 的时候没有编译对应类型的解码器)if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {LOGE("%s", "解码器无法打开\n");return;}


//准备读取//AVPacket 用于存储一帧一帧的压缩数据(H264)//缓冲区,开辟空间 AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));


//AVFrame 用于存储解码后的像素数据(YUV)//内存分配 AVFrame *yuv_frame = av_frame_alloc();AVFrame *rgb_frame = av_frame_alloc();


int got_picture, ret;int frame_count = 0;


//窗体 ANativeWindow *pWindow = ANativeWindow_fromSurface(env, surface);//绘制时的缓冲区 ANativeWindow_Buffer out_buffer;


//一帧一帧的读取压缩数据 while (av_read_frame(pFormatCtx, packet) >= 0) {//只要视频压缩数据(根据流的索引位置判断)if (packet->stream_index == v_stream_idx) {//7.解码一帧视频压缩数据,得到视频像素数据 ret = avcodec_decode_video2(pCodecCtx, yuv_frame, &got_picture, packet);if (ret < 0) {LOGE("%s", "解码错误");return;}


//为 0 说明解码完成,非 0 正在解码 if (got_picture) {


//lock window//设置缓冲区的属性:宽高、像素格式(需要与 Java 层的格式一致)ANativeWindow_setBuffersGeometry(pWindow, pCodecCtx->width, pCodecCtx->height,WINDOW_FORMAT_RGBA_8888);ANativeWindow_lock(pWindow, &out_buffer, NULL);


//初始化缓冲区//设置属性,像素格式、宽高//rgb_frame 的缓冲区就是 Window 的缓冲区,同一个,解锁的时候就会进行绘制 avpicture_fill((AVPicture *) rgb_frame, out_buffer.bits, AV_PIX_FMT_RGBA,pCodecCtx->width,pCodecCtx->height);


//YUV 格式的数据转换成 RGBA 8888 格式的数据, FFmpeg 也可以转换,但是存在问题,使用 libyuv 这个库实现 I420ToARGB(yuv_frame->data[0], yuv_frame->linesize[0],yuv_frame->data[2], yuv_frame->linesize[2],yuv_frame->data[1], yuv_frame->linesize[1],rgb_frame->data[0], rgb_frame->linesize[0],pCodecCtx->width, pCodecCtx->height);


//3、unlock windowANativeWindow_unlockAndPost(pWindow);


frame_count++;LOGI("解码绘制第 %d 帧", frame_count);}}


//释放资源 av_free_packet(packet);}


av_frame_free(&yuv_frame);avcodec_close(pCodecCtx);avformat_free_context(pFormatCtx);(*env)->ReleaseStringUTFChars(env, input_, input);}


#include "libswresample/swresample.h"


#define MAX_AUDIO_FRME_SIZE 48000 * 4


//音频解码(重采样)JNIEXPORT void JNICALLJava_com_haohao_ffmpeg_AVUtils_audioDecode(JNIEnv *env, jclass type, jstring input_,jstring output_) {//访问静态方法 jmethodID mid = (*env)->GetStaticMethodID(env, type, "onNativeCallback", "()V");const char *input = (*env)->GetStringUTFChars(env, input_, 0);const char *output = (*env)->GetStringUTFChars(env, output_, 0);


//注册组件 av_register_all();AVFormatContext *pFormatCtx = avformat_alloc_context();//打开音频文件 if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {LOGI("%s", "无法打开音频文件");return;}//获取输入文件信息 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGI("%s", "无法获取输入文件信息");return;}//获取音频流索引位置 int i = 0, audio_stream_idx = -1;for (; i < pFormatCtx->nb_streams; i++) {if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {audio_stream_idx = i;break;}}


//获取解码器 AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);if (codec == NULL) {LOGI("%s", "无法获取解码器");return;}//打开解码器 if (avcodec_open2(codecCtx, codec, NULL) < 0) {LOGI("%s", "无法打开解码器");return;}//压缩数据 AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));//解压缩数据 AVFrame *frame = av_frame_alloc();//frame->16bit 44100 PCM 统一音频采样格式与采样率 SwrContext *swrCtx = swr_alloc();


//重采样设置参数//输入的采样格式 enum AVSampleFormat in_sample_fmt = codecCtx->sample_fmt;//输出采样格式 16bit PCMenum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;//输入采样率 int in_sample_rate = codecCtx->sample_rate;//输出采样率 int out_sample_rate = 44100;//获取输入的声道布局//根据声道个数获取默认的声道布局(2 个声道,默认立体声 stereo)//av_get_default_channel_layout(codecCtx->channels);uint64_t in_ch_layout = codecCtx->channel_layout;//输出的声道布局(立体声)uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;


swr_alloc_set_opts(swrCtx,out_ch_layout, out_sample_fmt, out_sample_rate,in_ch_layout, in_sample_fmt, in_sample_rate,0, NULL);swr_init(swrCtx);


//输出的声道个数 int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);


//重采样设置参数


//位宽 16bit 采样率 44100HZ 的 PCM 数据 uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_FRME_SIZE);


FILE *fp_pcm = fopen(output, "wb");


int got_frame = 0, index = 0, ret;//不断读取压缩数据 while (av_read_frame(pFormatCtx, packet) >= 0) {//解码 ret = avcodec_decode_audio4(codecCtx, frame, &got_frame, packet);


if (ret < 0) {LOGI("%s", "解码完成");}//解码一帧成功 if (got_frame > 0) {LOGI("解码:%d", index++);swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE, frame->data, frame->nb_samples);//获取 sample 的 sizeint 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_free_packet(packet);}


fclose(fp_pcm);av_frame_free(&frame);av_free(out_buffer);


swr_free(&swrCtx);avcodec_close(codecCtx);avformat_close_input(&pFormatCtx);


(*env)->ReleaseStringUTFChars(env, input_, input);(*env)->ReleaseStringUTFChars(env, output_, output);//通知 Java 层解码完成(*env)->CallStaticVoidMethod(env, type, mid);}


JNIEXPORT void JNICALLJava_com_haohao_ffmpeg_AVUtils_audioPlay(JNIEnv *env, jclass type, jstring input_) {const char *input = (*env)->GetStringUTFChars(env, input_, 0);LOGI("%s", "sound");//注册组件 av_register_all();AVFormatContext *pFormatCtx = avformat_alloc_context();//打开音频文件 if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {LOGI("%s", "无法打开音频文件");return;}//获取输入文件信息 if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {LOGI("%s", "无法获取输入文件信息");return;}//获取音频流索引位置 int i = 0, audio_stream_idx = -1;for (; i < pFormatCtx->nb_streams; i++) {if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {audio_stream_idx = i;break;}}


//获取解码器 AVCodecContext *codecCtx = pFormatCtx->streams[audio_stream_idx]->codec;AVCodec *codec = avcodec_find_decoder(codecCtx->codec_id);if (codec == NULL) {LOGI("%s", "无法获取解码器");return;}

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
FFmpeg-之音视频解码与音视频同步(二)