本篇博文将给大家介绍一个FFMpeg在Android平台上的一个应用实例,实现视频转码格式转换。如果你还没了解如何通过CMake集成JNI开发环境和在Android Studio中集成FFMpeg,请阅读Android CMake集成JNI开发环境和Android Studio通过JNI(CMake方式)集成FFMpeg音视频处理框架。本人博客会长期更新Android FFmpeg,OpenGL和OpenCV,如果您感兴趣的话,可以关注我CSDN哦。

下面我们开始实现视频转码功能

项目结构如下

其中Java代码位于MainActivity下,C++代码位于native-lib.cpp下,在native-lib.cpp文件下调用FFmpeg库提供的函数库进行视频解码。

native-lib.cpp代码如下:

#include #include extern "C"{#include #include #include #include #include #include #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_jacket_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();//初始化一个AVFormatContext    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; inb_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 //把H264数据写入fp_h264文件                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    /**     * 当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。     * 因此需要通过“ flush_decoder”将这几帧数据输出。     * “flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递。     */    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_jacket_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);}}

在native-lib.cpp文件中,引入FFmpeg的库,调用FFmpeg库的相关C函数对视频流先进行分帧处理,然后对每一帧视频数据进行压缩转码处理,最后根据输出路径,生成对应的YUV文件。

MainActivity代码如下:

package com.jacket.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() {        ///sdcard/ych/videotemp/2018-03-23_1521792056455_camera.mp4        String folderurl= Environment.getExternalStorageDirectory().getPath()+"/ych/videotemp";//        String inputurl=folderurl+"/"+editText1.getText().toString();        String inputurl=folderurl+"/"+"2018-03-23_1521792056455_camera.mp4";        String outputurl=folderurl+"/"+editText2.getText().toString();        Log.e("ws-----------inputurl",inputurl);        Log.e("ws------------outputurl",outputurl);        decode(inputurl,outputurl);    }    public native int decode(String inputurl, String outputurl);    public native String avcodecinfo();}

拷贝视频ws2.mp4到手机SD卡下,进行编译转码即可得到对应的YUV文件
运行结果如下:


本篇博文源码在github,喜欢给个star,谢谢

更多相关文章

  1. NPM 和webpack 的基础使用
  2. 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
  3. 读取android手机流量信息
  4. android 使用html5作布局文件: webview跟javascript交互
  5. Android(安卓)多媒体扫描过程(Android(安卓)Media Scanner Proces
  6. android“设置”里的版本号
  7. Android开发环境搭建
  8. Android(安卓)Resource介绍和使用
  9. 2014.01.21 ——— android 关联android-support源码

随机推荐

  1. 数据库的CURD操作,PDO的本质与原理,预处
  2. 实例演示数据库的CURD操作、PDO的本质与
  3. MyCms 自媒体 CMS 系统 v3.3.1,支持 Swool
  4. 2022-Arch安装(详细)
  5. 表单、内联框架以及CSS基础
  6. PHP实例演示命名空间与类自动加载器
  7. 谷歌搜索官方给出的title标题优化,适用于
  8. PHP中实例演示CURD操作
  9. 1.markdown语法及部分html标签
  10. 预处理语句与存储过程