Android音视频-FFmpeg视频解码
2018-03-06 16:37
465 查看
我们这一篇主要了解FFmpeg的解码API,把Mp4解码出yuv视频数据格式。其中的主要C代码参考自雷神查看,本实例的功能在上一节的一个应用里面。
这里趟了一个坑,刚开始的时候把新的decode-lib添加到了native-lib一起,对于target_link_libraries的理解错误了,以为是所有的so链接进来到这里配置,并不是这样,它是把第一个例如native-lib这个target链接可以使用的库文件,后面都是可以链接使用的库文件:详细参考:查看
OK到这里运行项目一起OK并且也和雷神一样解码出了yuv格式的视频数据。
项目完整代码:查看
参考链接:查看
在Java层声明native方法并调用
我这里第一次把在res/raw下面的mp4文件拷贝到了存储卡上面去然后调用native方法:package com.lyman.ffmpeg_cmake; import android.content.Context; import android.os.Bundle; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class DecodeActivity extends AppCompatActivity { private static final String TAG = "DecodeActivity"; private TextView mDecodeFilePathTV; private String mDecodeFilePath; private ProgressBar mProgressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_decode); if (!getMoveFile().exists()) { new Thread(new Runnable() { @Override public void run() { copyFilesFromRaw(DecodeActivity.this, R.raw.i_am_you, "i_am_you.mp4", getExternalFilesDir(Environment.DIRECTORY_MOVIES).getAbsolutePath()); } }).start(); } mDecodeFilePathTV = findViewById(R.id.textView); mProgressBar = findViewById(R.id.progressBar); } public void onClickDecode(View view) { if (!getMoveFile().exists()) { 4000 Toast.makeText(this, "source file not exist", Toast.LENGTH_SHORT).show(); return; } if (mDecodeFilePath == null) { mDecodeFilePath = getExternalFilesDir(Environment.DIRECTORY_MOVIES).getAbsolutePath() + File.separator + "i_am_you.yuv"; } mProgressBar.setVisibility(View.VISIBLE); new Thread(new Runnable() { @Override public void run() { decode(getMoveFile().getAbsolutePath(), mDecodeFilePath); runOnUiThread(new Runnable() { @Override public void run() { mProgressBar.setVisibility(View.GONE); mDecodeFilePathTV.setText(mDecodeFilePath); } }); } }).start(); } public native int decode(String inputUrl, String outputUrl); static { // System.loadLibrary("avutil-55"); // System.loadLibrary("swresample-2"); // System.loadLibrary("swscale-4"); // System.loadLibrary("postproc-54"); // System.loadLibrary("avcodec-57"); // System.loadLibrary("avformat-57"); // System.loadLibrary("avfilter-6"); // System.loadLibrary("avdevice-57"); System.loadLibrary("decode-lib"); } private File getMoveFile() { File rootFile = getExternalFilesDir(Environment.DIRECTORY_MOVIES); // Create the storage directory if it does not exist if (!rootFile.exists()) { if (!rootFile.mkdirs()) { Log.d(TAG, "failed to create directory"); return null; } } String fileName = "i_am_you.mp4"; String path = rootFile.getAbsolutePath() + File.separator + fileName; return new File(path); } private void copyFilesFromRaw(Context context, int id, String fileName, String storagePath) { InputStream inputStream = context.getResources().openRawResource(id); File file = new File(storagePath); if (!file.exists()) {//如果文件夹不存在,则创建新的文件夹 file.mkdirs(); } readInputStream(storagePath + File.separator + fileName, inputStream); } /** * 读取输入流中的数据写入输出流 * * @param storagePath 目标文件路径 * @param inputStream 输入流 */ private void readInputStream(String storagePath, InputStream inputStream) { File file = new File(storagePath); try { if (!file.exists()) { // 1.建立通道对象 FileOutputStream fos = new FileOutputStream(file); // 2.定义存储空间 byte[] buffer = new byte[inputStream.available()]; // 3.开始读文件 int lenght = 0; while ((lenght = inputStream.read(buffer)) != -1) {// 循环从输入流读取buffer字节 // 将Buffer中的数据写到outputStream对象中 fos.write(buffer, 0, lenght); } fos.flush();// 刷新缓冲区 // 4.关闭流 fos.close(); inputStream.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
实现底层c代码
这个c文件和native-lib.c处于同一个目录下面,命名为decode-lib.c/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include <stdio.h> #include "include/libavcodec/avcodec.h" #include "include/libavformat/avformat.h" #include "include/libswscale/swscale.h" #include "include/libavutil/log.h" #include <libavutil/imgutils.h> #ifdef ANDROID #include <android/log.h> #define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__) #define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, "(^_^)", format, ##__VA_ARGS__) #else #define LOGE(format, ...) printf("(>_<) " format "\n", ##__VA_ARGS__) #define LOGI(format, ...) printf("(^_^) " format "\n", ##__VA_ARGS__) #endif /* Header for class com_lyman_ffmpeg_cmake_DecodeActivity */ #ifndef _Included_com_lyman_ffmpeg_cmake_DecodeActivity #define _Included_com_lyman_ffmpeg_cmake_DecodeActivity #ifdef __cplusplus extern "C" { #endif void custom_log(void *ptr, int level, const char *fmt, va_list vl) { FILE *fp = fopen("/storage/emulated/0/av_log.txt", "a+"); if (fp) { vfprintf(fp, fmt, vl); fflush(fp); fclose(fp); } } /* * Class: com_lyman_ffmpeg_cmake_DecodeActivity * Method: decode * Signature: (Ljava/lang/String;Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_lyman_ffmpeg_1cmake_DecodeActivity_decode (JNIEnv *env, jobject object, jstring input_jstr, jstring output_jstr) { AVFormatContext *pFormatCtx; int i, videoindex; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame, *pFrameYUV; uint8_t *out_buffer; AVPacket *packet; int y_size; int ret, got_picture; struct SwsContext *img_convert_ctx; FILE *fp_yuv; int frame_cnt; clock_t time_start, time_finish; double time_duration = 0.0; char input_str[500] = {0}; char output_str[500] = {0}; char info[1000] = {0}; sprintf(input_str, "%s", (*env)->GetStringUTFChars(env, input_jstr, NULL)); sprintf(output_str, "%s", (*env)->GetStringUTFChars(env, output_jstr, NULL)); //FFmpeg av_log() callback av_log_set_callback(custom_log); av_register_all(); avformat_network_init(); pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, input_str, NULL, NULL) != 0) { LOGE("Couldn't open input stream.\n"); return -1; } if (avformat_find_stream_info(pFormatCtx, NULL) < 0) { LOGE("Couldn't find stream information.\n"); return -1; } videoindex = -1; for (i = 0; i < pFormatCtx->nb_streams; i++) if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; break; } if (videoindex == -1) { LOGE("Couldn't find a video stream.\n"); return -1; } pCodecCtx = pFormatCtx->streams[videoindex]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (pCodec == NULL) { LOGE("Couldn't find Codec.\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { LOGE("Couldn't open codec.\n"); return -1; } pFrame = av_frame_alloc(); pFrameYUV = av_frame_alloc(); out_buffer = (unsigned char *) av_malloc( av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1)); av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1); packet = (AVPacket *) av_malloc(sizeof(AVPacket)); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); sprintf(info, "[Input ]%s\n", input_str); sprintf(info, "%s[Output ]%s\n", info, output_str); sprintf(info, "%s[Format ]%s\n", info, pFormatCtx->iformat->name); sprintf(info, "%s[Codec ]%s\n", info, pCodecCtx->codec->name); sprintf(info, "%s[Resolution]%dx%d\n", info, pCodecCtx->width, pCodecCtx->height); fp_yuv = fopen(output_str, "wb+"); if (fp_yuv == NULL) { printf("Cannot open output file.\n"); return -1; } frame_cnt = 0; time_start = clock(); while (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == videoindex) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) { LOGE("Decode Error.\n"); return -1; } if (got_picture) { sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); y_size = pCodecCtx->width * pCodecCtx->height; fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V //Output info char pictype_str[10] = {0}; switch (pFrame->pict_type) { case AV_PICTURE_TYPE_I: sprintf(pictype_str, "I"); break; case AV_PICTURE_TYPE_P: sprintf(pictype_str, "P"); break; case AV_PICTURE_TYPE_B: sprintf(pictype_str, "B"); break; default: sprintf(pictype_str, "Other"); break; } LOGI("Frame Index: %5d. Type:%s", frame_cnt, pictype_str); frame_cnt++; } } av_free_packet(packet); } //flush decoder //FIX: Flush Frames remained in Codec while (1) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); if (ret < 0) break; if (!got_picture) break; sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); int y_size = pCodecCtx->width * pCodecCtx->height; fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv); //Y fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv); //U fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv); //V //Output info char pictype_str[10] = {0}; switch (pFrame->pict_type) { case AV_PICTURE_TYPE_I: sprintf(pictype_str, "I"); break; case AV_PICTURE_TYPE_P: sprintf(pictype_str, "P"); break; case AV_PICTURE_TYPE_B: sprintf(pictype_str, "B"); break; default: sprintf(pictype_str, "Other"); break; } LOGI("Frame Index: %5d. Type:%s", frame_cnt, pictype_str); frame_cnt++; } time_finish = clock(); time_duration = (double) (time_finish - time_start); sprintf(info, "%s[Time ]%fms\n", info, time_duration); sprintf(info, "%s[Count ]%d\n", info, frame_cnt); sws_freeContext(img_convert_ctx); fclose(fp_yuv); av_frame_free(&pFrameYUV); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; } #ifdef __cplusplus } #endif #endif
配置CmakeLists.txt 文件
这里接着上一章节的配置下面进行配置,添加的配置代码如下:add_library( # Sets the name of the library. decode-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/decode-lib.c) target_link_libraries( native-lib libavcodec-57 libavdevice-57 libavfilter-6 libavformat-57 libavutil-55 libpostproc-54 libswresample-2 libswscale-4 ${log-lib}) target_link_libraries( decode-lib libavcodec-57 libavdevice-57 libavfilter-6 libavformat-57 libavutil-55 libpostproc-54 libswresample-2 libswscale-4 ${log-lib})
这里趟了一个坑,刚开始的时候把新的decode-lib添加到了native-lib一起,对于target_link_libraries的理解错误了,以为是所有的so链接进来到这里配置,并不是这样,它是把第一个例如native-lib这个target链接可以使用的库文件,后面都是可以链接使用的库文件:详细参考:查看
OK到这里运行项目一起OK并且也和雷神一样解码出了yuv格式的视频数据。
项目完整代码:查看
参考链接:查看
相关文章推荐
- Android 本地视频播放器开发 —— ffmpeg解码视频文件中的音频
- 视频学习笔记:Android ffmpeg解码多路h264视频并显示
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- android ffmpeg视频硬解码例子
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- Android 音视频编解码系列(1)--ubuntu下ndk编译ffmpeg0.8.1
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- Android音视频学习第2章:使用ffmpeg进行音频解码
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- Android平台下的FFmpeg的学习之路------(三)视频解码+NDK绘制
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- FFmpeg4Android:视频文件解码
- Android 音视频深入 九 FFmpeg解码视频生成yuv文件(附源码下载)
- Android 音视频深入 九 FFmpeg解码视频生成yuv文件(附源码下载)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(1)
- Android本地视频播放器开发--ffmpeg解码视频文件中的音频(2)
- Android+FFmpeg+ANativeWindow视频解码播放
- 视频学习笔记:Android ffmpeg解码多路h264视频并显示