使用FFmpeg解码H264-2016.01.14

时间:2023-03-08 17:19:48
使用FFmpeg解码H264-2016.01.14

使用jni方式调用FFmepg项目中接口,对H264裸码进行解码。

该Demo主要实现从文件中读取H264编码的视频流,然后使用FFmpeg解码,将解码后的码流保存到文件。

工程目录结构如图所示:

使用FFmpeg解码H264-2016.01.14

Android.mk文件内容如下

LOCAL_PATH := $(call my-dir)  

# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavcodec-.so
include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libavutil-.so
include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswresample-.so
include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := $(LOCAL_PATH)/libs/libswscale-.so
include $(PREBUILT_SHARED_LIBRARY) # Program
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec swscale avutil swresample
include $(BUILD_SHARED_LIBRARY)

Application.mk内容如下:

APP_ABI := armeabi

HelloJni.java内容如下:

package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle; public class HelloJni extends Activity
{
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState); TextView tv = new TextView(this);
if(DecodeH264Video())
{
tv.setText("Decode Video Success");
}
else
{
tv.setText("Decode Video Failed");
}
setContentView(tv);
} public native boolean DecodeH264Video(); static {
System.loadLibrary("avcodec-56");
System.loadLibrary("swscale-3");
System.loadLibrary("hello-jni");
System.loadLibrary("avutil-54");
System.loadLibrary("swresample-1");
}
}

hello-jni.c文件内容如下:

#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include <stdio.h>
#include <string.h>
#include <jni.h>
#include <android/log.h> typedef enum
{
FALSE = , TRUE = ,
} C_BOOL; typedef unsigned char uint8_t;
const int IN_BUFFER_SIZE = ; #define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#define LOGD(format, ...) __android_log_print(ANDROID_LOG_DEBUG, "(-_-)", format, ##__VA_ARGS__) static C_BOOL __DecodeH264Video(FILE* fp_in, FILE* fp_out); JNIEXPORT jboolean JNICALL Java_com_example_hellojni_HelloJni_DecodeH264Video(JNIEnv *env, jobject obj)
{
char filepath_in[] = "/data/video/bxjg_352x288.h264";
FILE *fp_in = fopen(filepath_in, "rb");
if (NULL == fp_in)
{
LOGE("open input h264 video file failed, filename [%s]", filepath_in);
return (jboolean) FALSE;
} char filepath_out[] = "/data/video/bxjg_352x288.yuv";
FILE *fp_out = fopen(filepath_out, "wb");
if (NULL == fp_out)
{
LOGE("open output yuv video file failed, filename [%s]", filepath_out);
return (jboolean) FALSE;
} LOGD("open input and output file success"); if (TRUE == __DecodeH264Video(fp_in, fp_out))
{
LOGD("decode h264 video success");
}
else
{
LOGE("decode h264 video failed");
return (jboolean) FALSE;
} fclose(fp_in);
fclose(fp_out); return (jboolean) TRUE;
} C_BOOL __DecodeH264Video(FILE* fp_in, FILE* fp_out)
{
avcodec_register_all(); AVCodec *pCodec = NULL;
pCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (NULL == pCodec)
{
LOGE("avcodec_find_decoder failed");
return FALSE;
} AVCodecContext *pCodecCtx = NULL;
pCodecCtx = avcodec_alloc_context3(pCodec);
if (NULL == pCodecCtx)
{
LOGE("avcodec_alloc_context3 failed");
return FALSE;
} AVCodecParserContext *pCodecParserCtx = NULL;
pCodecParserCtx = av_parser_init(AV_CODEC_ID_H264);
if (NULL == pCodecParserCtx)
{
LOGE("av_parser_init failed");
return FALSE;
} if (avcodec_open2(pCodecCtx, pCodec, NULL) < )
{
LOGE("avcodec_open2 failed");
return FALSE;
} AVFrame *pFrame = NULL;
pFrame = av_frame_alloc();
if (NULL == pFrame)
{
LOGE("av_frame_alloc failed");
return FALSE;
} AVPacket packet;
av_init_packet(&packet); uint8_t in_buffer[IN_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE];
memset(in_buffer, , sizeof(in_buffer));
uint8_t *cur_ptr = NULL;
int cur_size = ;
int ret = ;
int got_picture = ;
int y_size = ;
int first_time = ; struct SwsContext *img_convert_ctx = NULL;
AVFrame *pFrameYUV = NULL;
uint8_t *out_buffer = NULL; while (TRUE)
{
cur_size = fread(in_buffer, , IN_BUFFER_SIZE, fp_in);
if ( == cur_size)
{
break;
} cur_ptr = in_buffer;
while (cur_size > )
{
int parse_len = av_parser_parse2(pCodecParserCtx, pCodecCtx, &packet.data, &packet.size, cur_ptr, cur_size,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE); cur_ptr += parse_len;
cur_size -= parse_len; if ( == packet.size)
{
continue;
} LOGD("packet size [%d]", packet.size); switch (pCodecParserCtx->pict_type)
{
case AV_PICTURE_TYPE_I:
{
LOGD("AV_PICTURE_TYPE_I");
break;
}
case AV_PICTURE_TYPE_P:
{
LOGD("AV_PICTURE_TYPE_P");
break;
}
case AV_PICTURE_TYPE_B:
{
LOGD("AV_PICTURE_TYPE_B");
break;
}
default:
{
LOGD("OTHER_PICTURE_TYPE");
break;
}
} LOGD("CodecParserCtx->output_picture_number [%d]", pCodecParserCtx->output_picture_number); ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet); if (ret < )
{
LOGE("avcodec_decode_video2 failed");
return FALSE;
} if (got_picture)
{
if (first_time)
{
LOGD("CodecCtx->codec->long_name [%s]", pCodecCtx->codec->long_name);
LOGD("CodecCtx->width [%d], CodecCtx->height [%d]", pCodecCtx->width, pCodecCtx->height); //SwsContext
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); pFrameYUV = av_frame_alloc(); out_buffer = (uint8_t *) av_malloc(
avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); avpicture_fill((AVPicture *) pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width,
pCodecCtx->height); y_size = pCodecCtx->width * pCodecCtx->height; first_time = ;
} sws_scale(img_convert_ctx, (const uint8_t* const *) pFrame->data, pFrame->linesize, ,
pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); fwrite(pFrameYUV->data[], , y_size, fp_out); //Y
fwrite(pFrameYUV->data[], , y_size / , fp_out); //U
fwrite(pFrameYUV->data[], , y_size / , fp_out); //V LOGD("succeed to decode one frame"); }
} } //Flush Decoder
packet.data = NULL;
packet.size = ; while (TRUE)
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, &packet);
if (ret < )
{
LOGE("avcodec_decode_video2 failed");
return FALSE;
} if (!got_picture)
{
break;
} if (got_picture)
{ sws_scale(img_convert_ctx, (const uint8_t* const *) pFrame->data, pFrame->linesize, , pCodecCtx->height,
pFrameYUV->data, pFrameYUV->linesize); fwrite(pFrameYUV->data[], , y_size, fp_out); //Y
fwrite(pFrameYUV->data[], , y_size / , fp_out); //U
fwrite(pFrameYUV->data[], , y_size / , fp_out); //V LOGD("Flush Decoder: Succeed to decode 1 frame");
}
} sws_freeContext(img_convert_ctx);
av_frame_free(&pFrameYUV);
av_parser_close(pCodecParserCtx);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
av_free(pCodecCtx); return TRUE;
}

如果仅使用FFmpeg中的解码功能,可用如下的配置选项对编译的库进行瘦身,使得编译出来的解码动态库libavcodec.so大小为2M左右!

#!/bin/bash
NDK="/home/alchen/android-ndk-r9d"
TARGET="android-19"
SYSROOT="$NDK/platforms/$TARGET/arch-arm"
TOOLCHAIN="$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64" function build_one
{
./configure \
--prefix=$PREFIX \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--target-os=linux \
--enable-decoder=h264 \
--enable-shared \
--enable-version3 \
--enable-gpl \
--enable-nonfree \
--enable-protocol=file \
--enable-avfilter \
--enable-cross-compile \
--enable-asm \
--enable-neon \
--enable-armv5te \
--disable-static \
--disable-decoders \
--disable-doc \
--disable-muxers \
--disable-demuxers \
--disable-bsfs \
--disable-indevs \
--disable-outdevs \
--disable-filters \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffserver \
--disable-ffprobe \
--disable-encoders \
--disable-devices \
--disable-protocols \
--disable-network \
--disable-avdevice \
--arch=arm \
--sysroot=$SYSROOT \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make
make install
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one