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 文件就是了。