ffmpeg实战教程(六)Android CMake实现解码(MP4转YUV)
来源:互联网 发布:b2b平台飞单 知乎 编辑:程序博客网 时间:2024/06/08 14:12
在前面的文章中我们用ffmpeg在windows上实现了多种功能,下面系列的文章我们将移植到Android系统上实现。
我们将使用最新版:
最新版ffmpeg ffmpeg3.3
新版Android studio Android studio2.3
新版JNI编译方式 CMake
对于Android开发者或许对C/C++/JNI知识不够了解。
可以先看这里:
C语言小结 :http://blog.csdn.net/king1425/article/details/70256764
C++小结(一) : http://blog.csdn.net/king1425/article/details/70260091
JNI高阶知识总结 : http://blog.csdn.net/king1425/article/details/71405131
下面我们开始实现今天的功能:
1.编译Android版ffmpeg并移植到Android studio中
看这里:
windows下编译最新版ffmpeg3.3-android,并通过CMake方式移植到Android studio2.3中
http://blog.csdn.net/king1425/article/details/70338674
2.复制一份第一步移植成功的的代码命名为:ffmpegandroiddecoder
3.删减部分代码验证无误,然后编写布局代码逻辑代码
点击codec按钮出现如上图列表界面则无误
下面是相关代码:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_codec" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="2dp" android:text="Codec" android:textAllCaps="false" /> </LinearLayout> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="12dp" android:text="ws2.mp4" android:id="@+id/editText1" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="12dp" android:text="ws2.yuv" android:id="@+id/editText2" /> <Button android:text="开始解码为YUV" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/button" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_info" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Hello World!" /> </ScrollView></LinearLayout>
package com.ws.ffmpegandroiddecoder;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.Button;import android.widget.EditText;import android.widget.TextView;public class MainActivity extends AppCompatActivity implements View.OnClickListener { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } private Button codec; private TextView tv_info; private EditText editText1,editText2; private Button btnDecoder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { codec = (Button) findViewById(R.id.btn_codec); tv_info= (TextView) findViewById(R.id.tv_info); editText1 = (EditText) findViewById(R.id.editText1); editText2 = (EditText) findViewById(R.id.editText2); btnDecoder= (Button) findViewById(R.id.button); codec.setOnClickListener(this); btnDecoder.setOnClickListener(this); } @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_codec: tv_info.setText(avcodecinfo()); break; case R.id.button: startDecode(); break; default: break; } } private void startDecode() { String folderurl= Environment.getExternalStorageDirectory().getPath(); String inputurl=folderurl+"/"+editText1.getText().toString(); String outputurl=folderurl+"/"+editText2.getText().toString(); Log.e("ws-----------inputurl",inputurl); Log.e("ws------------outputurl",outputurl); decode(inputurl,outputurl); } public native String decode(String inputurl, String outputurl); public native String avcodecinfo();}
4.实现native方法 decode(String inputurl, String outputurl)
对ffmeg API不了解的同学先看这里: ffmpeg源码简析(一)结构总览:http://blog.csdn.net/king1425/article/details/70597642
#include <jni.h>#include <string>extern "C"{#include <libavutil/log.h>#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>#include <libavutil/imgutils.h>#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__)//Output FFmpeg's av_log()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); }}JNIEXPORT jint JNICALLJava_com_ws_ffmpegandroiddecoder_MainActivity_decode( JNIEnv *env, jobject , 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(input_jstr, NULL)); sprintf(output_str,"%s",env->GetStringUTFChars(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;}jstringJava_com_ws_ffmpegandroiddecoder_MainActivity_avcodecinfo( JNIEnv *env, jobject) { char info[40000] = {0}; av_register_all(); AVCodec *c_temp = av_codec_next(NULL); while (c_temp != NULL) { if (c_temp->decode != NULL) { sprintf(info, "%sdecode:", info); } else { sprintf(info, "%sencode:", info); } switch (c_temp->type) { case AVMEDIA_TYPE_VIDEO: sprintf(info, "%s(video):", info); break; case AVMEDIA_TYPE_AUDIO: sprintf(info, "%s(audio):", info); break; default: sprintf(info, "%s(other):", info); break; } sprintf(info, "%s[%10s]\n", info, c_temp->name); c_temp = c_temp->next; } return env->NewStringUTF(info);}}
5.拷贝ws.mp4到SD卡根目录,编译运行
GG编译报错(⊙o⊙),不慌不慌,链接库报错,应该是CMakeLists.txt配置问题。
在之前一章配置的target_link_libraries基础上,更改如下即可
target_link_libraries( native-lib avutil-55 swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57 ${log-lib} )
6.点击开始解码按钮,然后看SD卡跟目录是否多了一个ws.yuv文件
OK~success
请大家务必对着源码观看博文,否则如盲人摸象(⊙o⊙)
源码:https://github.com/WangShuo1143368701/FFmpegAndroid/tree/master/ffmpegandroiddecoder
- ffmpeg实战教程(六)Android CMake实现解码(MP4转YUV)
- ffmpeg实战教程(一)Mp4,mkv等格式解码为h264和yuv数据
- ffmpeg实战教程(七)Android CMake avi解码后SurfaceView显示
- ffmpeg实战教程(二)用SDL播放YUV,并结合ffmpeg实现简易播放器
- ffmpeg实战教程(四)格式转换如MP4转MKV等
- ffmpeg实战教程(三)音频PCM采样为AAC,视频YUV编码为H264/HEVC
- android studio 2.2 cmake ffmpeg 简单的播放器(这里只有视频解码)
- 在Andorid中使用FFmpeg实现YUV编码为MP4
- 视频编解码学习(六):YUV格式学习
- ffmpeg实战教程(八)Android平台下AVfilter 实现水印,滤镜等特效功能
- ffmpeg实现yuv转mpeg
- 使用ffmpeg实现解码并保存为yuv文件
- 在Andorid中使用FFmpeg实现YUV解码为BMP
- Android Studio中使用FFMPEG(CMake)
- (转)FFMPEG解码流程
- FFMPEG解码流程(转)
- FFMPEG解码流程(转)
- FFMPEG解码流程(转)
- salesforce中workflow rules和process builder的应用场景及局限研究
- Linux/deepin下Qt5无法输入中文的解决方法
- Dapper .NET Tutorial I
- 千万别做程序员?
- LocalBroadcastManager源码解析
- ffmpeg实战教程(六)Android CMake实现解码(MP4转YUV)
- 产生死锁的原因和必要条件+解决死锁的基本方法
- MYSQL数据库的数据目录无缝更换记载
- percona插件监控mysql
- java__for if10以内从1开始相加大于20输出
- 去除快捷方式小箭头
- 做程序猿的老婆应该注意的一些事情
- ==与equals方法的区别
- mysql 用户权限控制、远程访问控制