1、最近做的一个项目涉及到从服务端获取H264编码的帧数据解码并在Android的Surface上显示的处理,从开始学习ffmpeg解码这块发现网上的资料很少,特别是涉及到解码并在Android平台上用Surface刷新显示数据,故将所有代码分享如下。从GitHub上找到了一份版本2.2的ffmpeg的源码,里面包括ffmpeg的所有源码,及H264 解码实现的范例,和ffmpeg编译的sh脚本;现已上传到csdn上面,下载链接:http://download.csdn.net/detail/air798/9074547
2、因需要先编译一个H264解码 .a 静态库,再利用这个静态库与Native的Socket消息发送和接收及Surface刷新显示的cpp文件再一起编译生成最终的.so动态库,所以修改h264decoder.c的实现。此处ffmpeg版本采用的是官网的最新版本。
#include #include
#include
#include
#include
#include
#include
#include
#include "h264decoder.h"
#define D(x...) __android_log_print(ANDROID_LOG_ERROR, "h264decoder", x)
typedef struct DecoderContext {
int color_format;
struct AVCodec *codec;
struct AVCodecContext *codec_ctx;
struct AVFrame *src_frame;
struct AVFrame *dst_frame;
struct SwsContext *convert_ctx;
int frame_ready;
} DecoderContext;
DecoderContext *ctx = NULL;
int i, j, k;
void av_log_callback(void *ptr, int level, const char *fmt, __va_list vl) {
static char line[1024] = { 0 };
vsnprintf(line, sizeof(line), fmt, vl);
D(line);
}
void initH264Decoder() {
D("Create native H264 decoder context");
av_register_all();
av_log_set_callback(av_log_callback);
ctx = calloc(1, sizeof(DecoderContext));
//在Android平台上能兼容所有平台的显示格式貌似只有 RGB565
ctx->color_format =
AV_PIX_FMT_RGB565; //AV_PIX_FMT_RGB24 AV_PIX_FMT_RGB565 PIX_FMT_YUV420P
ctx->codec = avcodec_find_decoder(CODEC_ID_H264);
ctx->codec_ctx = avcodec_alloc_context3(ctx->codec);
ctx->codec_ctx->pix_fmt =
AV_PIX_FMT_YUVJ420P;
// 此处的值是你要所解码的帧数据格式
ctx->codec_ctx->flags2 |= CODEC_FLAG2_CHUNKS;
ctx->src_frame = av_frame_alloc();
ctx->dst_frame = av_frame_alloc();
avcodec_open2(ctx->codec_ctx, ctx->codec, NULL);
}
void destroyH264Decoder() {
D("Destroy native H264 decoder context");
avcodec_close(ctx->codec_ctx);
av_free(ctx->codec_ctx);
av_free(ctx->src_frame);
av_free(ctx->dst_frame);
free(ctx);
}
int decodeH264Buffer(char *inBuffer, size_t num_bytes, long pkt_pts, char *outBuffer) {
AVPacket packet = {
.data = (uint8_t*) inBuffer, // 接收到的帧数据
.size = num_bytes, // 帧数据大小
.pts =(int64_t) pkt_pts // 帧数据时间戳
};
int frameFinished = 0;
int res = avcodec_decode_video2(ctx->codec_ctx, ctx->src_frame,
&frameFinished, &packet);
if (res < 0) {
return -1;
}
if (frameFinished) {// 判断解码是否成功
int frameSize = avpicture_get_size(ctx->color_format,
ctx->codec_ctx->width, ctx->codec_ctx->height);// 根据颜色空间计算需要转换的帧数据大小
uint8_t *buffer = (uint8_t *) av_malloc(frameSize);//申请内存填充转换的帧数据
avpicture_fill((AVPicture*) ctx->dst_frame, (unsigned char *) buffer,
ctx->color_format, ctx->codec_ctx->width,
ctx->codec_ctx->height);
ctx->convert_ctx = sws_getContext(ctx->codec_ctx->width,
ctx->codec_ctx->height, ctx->codec_ctx->pix_fmt,
ctx->codec_ctx->width, ctx->codec_ctx->height,
ctx->color_format, SWS_FAST_BILINEAR, NULL, NULL, NULL);// 获取转换的context参数
// 关于SWS_FAST_BILINEAR 参数说明链接: http://blog.csdn.net/mashen1989/article/details/7835508
if (ctx->convert_ctx == NULL) {
return -1;
}
// rotate image 从其他地方查找的资料显示要旋转解码的帧数据,这样解码出来的帧图像才不会是倒着的,但是使用过程中没出现这个问题,此处就不做图像旋转;
/* ctx->src_frame->data[0] += ctx->src_frame->linesize[0]
* (ctx->codec_ctx->height - 1);
ctx->src_frame->linesize[0] *= -1;
ctx->src_frame->data[1] += ctx->src_frame->linesize[1]
* (ctx->codec_ctx->height / 2 - 1);
ctx->src_frame->linesize[1] *= -1;
ctx->src_frame->data[2] += ctx->src_frame->linesize[2]
* (ctx->codec_ctx->height / 2 - 1);
ctx->src_frame->linesize[2] *= -1; */
sws_scale(ctx->convert_ctx, (const uint8_t**) ctx->src_frame->data,
ctx->src_frame->linesize, 0, ctx->codec_ctx->height,
ctx->dst_frame->data, ctx->dst_frame->linesize); // image colour format translate
// Android平台的Native Surface上刷新显示的数据需要32位对齐,所以必须判断一下帧数据的宽度是否刚好能整除32,如果不是,就逐行拷贝数据;
//此处直接拷贝buffer中的数据,如果转化的是RGB格式,直接拷贝dst_frame->data[0]中的数据也行;如果是YUV数据则要拷贝dst_frame->data[0]、dst_frame->data[1]、dst_frame->data[2]中的数据
if (ctx->codec_ctx->width % 32 == 0) {
memcpy(outBuffer, buffer, frameSize);
} else {
int delta = 32 - (ctx->codec_ctx->width * 2) % 32;
for (int i = 0; i < ctx->codec_ctx->height; i++) {
memcpy(outBuffer + i * (ctx->codec_ctx->width * 2 + delta),
buffer + i * ctx->codec_ctx->width * 2,
ctx->codec_ctx->width * 2);
}
}
av_free(buffer);
}
return frameFinished;
}
int getWidth() {
return ctx->codec_ctx->width;
}
int getHeight() {
return ctx->codec_ctx->height;
}
int getSize() {
return avpicture_get_size(ctx->color_format, ctx->codec_ctx->width,
ctx->codec_ctx->height);
}
相应的h264decoder.h头文件代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
void initH264Decoder();
void destroyH264Decoder();
int getWidth();
int getHeight();
int getSize();
void av_log_callback(void *ptr, int level, const char *fmt, __va_list vl);
int decodeH264Buffer(char *inBuffer, size_t num_bytes, long pkt_pts,char *outBuffer);
#ifdef __cplusplus
}
#endif
sh脚本中需要修改的参数如下:
NDK=~/......./android-ndk-r9d // Linux编译环境下你放置的ndk路径
ARM_PLATFORM=$NDK/platforms/android-5/arch-arm/
ARM_PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86
X86_PLATFORM=$NDK/platforms/android-9/arch-x86/
X86_PREBUILT=$NDK/toolchains/x86-4.6/prebuilt/linux-x86
编译.a静态库的Android.mk脚本修改如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libh264decoder
LOCAL_SRC_FILES := h264decoder.c
#ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) $(LOCAL_PATH)/ffmpeg-android/armv7-a/include
LOCAL_LDFLAGS := $(LOCAL_PATH)/ffmpeg-android/armv7-a/lib/libavformat.a \
$(LOCAL_PATH)/ffmpeg-android/armv7-a/lib/libavcodec.a \
$(LOCAL_PATH)/ffmpeg-android/armv7-a/lib/libavutil.a \
$(LOCAL_PATH)/ffmpeg-android/armv7-a/lib/libswresample.a \
$(LOCAL_PATH)/ffmpeg-android/armv7-a/lib/libswscale.a
#endif
LOCAL_CFLAGS := -std=c99 -Wall -fvisibility=hidden
LOCAL_LDFLAGS := $(LOCAL_LDFLAGS) -Wl,--gc-sections
LOCAL_LDLIBS := -llog -lavformat -lavcodec -lswscale -lavutil
include $(BUILD_STATIC_LIBRARY)