jni 学习-ffmpeg simpleDecoder

ffmpeg 简单的解码器:

1.新建java 类

public class SimpleDecoder {

    static {
        System.loadLibrary("avcodec");
        System.loadLibrary("avdevice");
        System.loadLibrary("avfilter");
        System.loadLibrary("avformat");
        System.loadLibrary("avutil");
        System.loadLibrary("postproc");
        System.loadLibrary("swresample");
        System.loadLibrary("swresample");
        System.loadLibrary("native-lib");

    }


    public static native int decode(String inputUrl,String outPutUrl);
    public static native String helloFromJni();

}

2.编写c 实现代码

解码流程:

       1)初始化

// av_register_all(); 此方法已经过时,无需调用,并且调了也不会做任何事情
avformat_network_init();

     2)AVFormatContext获取和初始化

//AVFormatContext获取
avformat_alloc_context()
//打开文件,和AVFormatContext关联
avformat_open_input()
//获取文件流信息
avformat_find_stream_info()

3)获取解码器

//AVCodecContext获取
avcodec_alloc_context3()
//将AVCodecParameters转换为AVCodecContext
avcodec_parameters_to_context()
//获取解码器
avcodec_find_decoder()
//打开解码器
avcodec_open2()

4)解码准备

//获取解码数据包装 AVFrame
av_frame_alloc()
//根据宽高,解码类型(yuv420)获取缓存buffer大小
av_image_get_buffer_size()
//根据指定的图像参数和提供的数组设置数据指针和行数 ,数据填充到对应的AVFrame里面
av_image_fill_arrays()
//获取编码数据 包装 AVPacket
av_packet_alloc()
//获取SwsContext 图片转换(宽高这些)需要用到
sws_getContext()

5)读取数据源解码存储

//读取编码数据源到AVPacket
av_read_frame()
//发送数据源    
avcodec_send_packet()
//解码数据源  ,和avcodec_send_packet配合使用
avcodec_receive_frame()
//图像转换
sws_scale()
//写入文件
fwrite()

6)释放资源

 

源码:

#include <stdio.h>
#include <jni.h>

extern "C" {

void customlog(void *ptr, int level, const char *fmt, va_list vaList) {
    FILE *fp = fopen("/storage/emulated/0/av_log.txt", "a+");
    if (fp) {
        vfprintf(fp, fmt, vaList);
        fflush(fp);
        fclose(fp);
    }
}

#include <time.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/log.h"

#ifdef ANDROID

#include <jni.h>
#include <android/log.h>
#include <libavutil/imgutils.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

JNIEXPORT jint JNICALL
Java_demo_com_testndk_ffmpeg_SimpleDecoder_decode(JNIEnv *env, jobject instance, jstring inputUrl_,
                                                  jstring outPutUrl_) {
    const char *inputUrl = env->GetStringUTFChars(inputUrl_, 0);
    const char *outPutUrl = env->GetStringUTFChars(outPutUrl_, 0);
    char info[1000] = {0};
    AVFormatContext *avFormatContext;
    int videoIndex;
    AVCodecContext *avCodecContext;
    AVCodec *avCodec;
    AVFrame *pFrame, *yuvFrame;
    u_int8_t *outBuffer;
    AVPacket *avPacket;
    int ysize, got_picture;
    int ret;
    struct SwsContext *img_convert_ctx;
    FILE *fp_yuv;
    int frame_cnt;
    clock_t time_start, time_end;
    double time_duration = 0.0;
    // TODO
    av_log_set_callback(customlog);
    avFormatContext = avformat_alloc_context();
    //网上说已经过期了,不用调用注册函数,过时的函数不会做任何事情av_register_all();
    int errocode = avformat_open_input(&avFormatContext, inputUrl, NULL, NULL);

    if (errocode != 0) {
        LOGE("avformat_open_input() called failed: %s", av_err2str(errocode));
        return -1;
    }

    if (avformat_find_stream_info(avFormatContext, NULL) < 0) {
        LOGE("couldn't find stream information .\n");
        return -1;
    }
    videoIndex = -1;
    for (int i = 0; i < avFormatContext->nb_streams; ++i) {
        if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoIndex = i;
            break;
        }
    }
    if (videoIndex == -1) {
        LOGE("couldn't find a video stream.\n");
    }
    avCodecContext = avcodec_alloc_context3(NULL);
    if (avcodec_parameters_to_context(avCodecContext,
                                      avFormatContext->streams[videoIndex]->codecpar) < 0) {
        LOGE("Didn't parameters to contex.");
        return -1;
    }
    avCodec = avcodec_find_decoder(avCodecContext->codec_id);
    if (avCodec == NULL) {
        LOGE("couldn't find codec.\n");
        return -1;
    }

    if (avcodec_open2(avCodecContext, avCodec, NULL)) {
        LOGE("couldn't open codec.\n");
        return -1;
    }
    pFrame = av_frame_alloc();
    yuvFrame = av_frame_alloc();

    outBuffer = (unsigned char *) av_malloc(
            av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                     avCodecContext->width,
                                     avCodecContext->height, 1));

    int error = av_image_fill_arrays(yuvFrame->data, yuvFrame->linesize,
                                     outBuffer, AV_PIX_FMT_YUV420P, avCodecContext->width,
                                     avCodecContext->height, 1);
    if (error < 0) {
        LOGE("%s", av_err2str(errocode));
    }

    avPacket = av_packet_alloc();
    LOGE("--------------- File Information ----------------");
    av_dump_format(avFormatContext, 0, inputUrl, 0);
    LOGE("-------------------------------------------------");

    img_convert_ctx = sws_getContext(avCodecContext->width, avCodecContext->height,
                                     avCodecContext->pix_fmt,
                                     avCodecContext->width, avCodecContext->height,
                                     AV_PIX_FMT_YUV420P,
                                     SWS_BICUBIC, NULL, NULL, NULL);
//如果这里有异常加上这个 可以输出异常信息,虽然看不懂
    if (env->ExceptionOccurred()) {
        env->ExceptionDescribe(); // writes to logcat
        env->ExceptionClear();
        return 1;
    }
    sprintf(info, "[Input     ]%s\n", inputUrl);
    sprintf(info, "%s[Output    ]%s\n", info, outPutUrl);
    sprintf(info, "%s[Format    ]%s\n", info, avFormatContext->iformat->name);
    sprintf(info, "%s[Codec     ]%s\n", info, avCodecContext->codec->name);
    sprintf(info, "%s[Resolution]%dx%d\n", info, avCodecContext->width, avCodecContext->height);

    fp_yuv = fopen(outPutUrl, "wb+");
    if (fp_yuv == NULL) {
        LOGE("couldn't open output file.\n");
        return -1;
    }
    frame_cnt = 0;
    time_start = clock();
    while (av_read_frame(avFormatContext, avPacket) >= 0) {
        if (avPacket->stream_index == videoIndex) {
            if (avcodec_send_packet(avCodecContext, avPacket) != 0) {
                LOGE("fail to send packet");
                return -1;
            }
            ret = avcodec_receive_frame(avCodecContext, pFrame);
            if (ret < 0) {
                LOGE("Decode Error.\n");
                return -1;
            }
            sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,
                      0,
                      avCodecContext->height,
                      yuvFrame->data, yuvFrame->linesize);
            ysize = avCodecContext->width * avCodecContext->height;

            fwrite(yuvFrame->data[0], 1, ysize, fp_yuv);    //Y
            fwrite(yuvFrame->data[1], 1, ysize / 4, fp_yuv);  //U
            fwrite(yuvFrame->data[2], 1, ysize / 4, fp_yuv);  //V

            char pic_type_str[10] = {0};
            switch (pFrame->pict_type) {
                case AV_PICTURE_TYPE_I:
                    sprintf(pic_type_str, "I");
                    break;
                case AV_PICTURE_TYPE_P:
                    sprintf(pic_type_str, "P");
                    break;
                case AV_PICTURE_TYPE_B:
                    sprintf(pic_type_str, "B");
                    break;
                default:
                    sprintf(pic_type_str, "Other");
                    break;
            }
            LOGI("Frame Index: %5d. Type:%s", frame_cnt, pic_type_str);
            frame_cnt++;
        }
        av_packet_unref(avPacket);
    }

//flush decoder
    /*当av_read_frame()循环退出的时候,实际上解码器中可能还包含剩余的几帧数据。
    因此需要通过“flush_decoder”将这几帧数据输出。
    “flush_decoder”功能简而言之即直接调用avcodec_decode_video2()获得AVFrame,而不再向解码器传递AVPacket。*/
    while (1) {
        if (avcodec_send_packet(avCodecContext, avPacket) != 0) {
            LOGE("fail to send packet");
            break;
        }
        ret = avcodec_receive_frame(avCodecContext, pFrame);
        if (ret < 0) {
            LOGE("Decode Error.\n");
            break;
        }
        sws_scale(img_convert_ctx, (const uint8_t *const *) pFrame->data, pFrame->linesize,
                  0,
                  avCodecContext->height,
                  yuvFrame->data, yuvFrame->linesize);
        ysize = avCodecContext->width * avCodecContext->height;

        fwrite(yuvFrame->data[0], 1, ysize, fp_yuv);    //Y
        fwrite(yuvFrame->data[1], 1, ysize / 4, fp_yuv);  //U
        fwrite(yuvFrame->data[2], 1, ysize / 4, fp_yuv);  //V

        char pic_type_str[10] = {0};
        switch (pFrame->pict_type) {
            case AV_PICTURE_TYPE_I:
                sprintf(pic_type_str, "I");
                break;
            case AV_PICTURE_TYPE_P:
                sprintf(pic_type_str, "P");
                break;
            case AV_PICTURE_TYPE_B:
                sprintf(pic_type_str, "B");
                break;
            default:
                sprintf(pic_type_str, "Other");
                break;
        }
        LOGI("Frame Index: %5d. Type:%s", frame_cnt, pic_type_str);
        frame_cnt++;
    }


    time_end = clock();
    time_duration = time_end - time_start;
    sprintf(info, "%s[Time      ]%fms\n", info, time_duration);
    sprintf(info, "%s[Count     ]%d\n", info, frame_cnt);

    LOGE("%s",info);
    sws_freeContext(img_convert_ctx);

    fclose(fp_yuv);

    av_frame_free(&yuvFrame);
    av_frame_free(&pFrame);

    avcodec_free_context(&avCodecContext);
    avformat_free_context(avFormatContext);
    av_packet_free(&avPacket);
    av_free(outBuffer);

    env->ReleaseStringUTFChars(inputUrl_, inputUrl);
    env->ReleaseStringUTFChars(outPutUrl_, outPutUrl);
    return 0;
}

}

运行的时候遇到过几个坑!

1)程序闪退,没有任何日志。这里是c 的代码有问题,很难调试,加了断点之后虽然确定了出错的方法,但是仍然不知道怎么改!

void customlog(void *ptr, int level, const char *fmt, va_list vaList) {
    FILE *fp = fopen("/storage/emulated/0/av_log.txt", "a+");
    if (fp) {
//fprintf(fp, fmt, vaList);这里之前是 fprintf 一直闪退改成 vfprintf 就好了
        vfprintf(fp, fmt, vaList);
        fflush(fp);
        fclose(fp);
    }
}
//这里报错 没有权限 android 6.0之后要在程序里面动态申请权限
//avFormatContext 可以为空,inputUrl 就是绝对路径
 int errocode = avformat_open_input(&avFormatContext, inputUrl, NULL, NULL);

    if (errocode != 0) {
        LOGE("avformat_open_input() called failed: %s", av_err2str(errocode));
        return -1;
    }

 

avcodec_parameters_to_context()这个不能忘,不然 avcodec_open2会闪退

最后执行完释放资源之后记得 将返回值返回,之前忽略了返回值,不停的闪退,注释了n多代码还是闪退,最后发现需要一个返回值!

 

总结:40M的.mp4 文件解码之后居然是 3G 多!

使用yuv 播放器播放的时候要设置分辨率的,不然就是下面这种

设置好分辨率

这个yuvplayer 的链接:https://github.com/latelee/YUVPlayer bin文件下的exe 文件就是了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值