您的位置:首页 > 移动开发 > Android开发

Android使用FFmpeg 解码H264并播放(二)

2017-06-12 17:19 555 查看
上一节记录了Android使用FFmpeg环境搭建过程。这一节记录视频解码过程。

问题描述

在开发中使用某摄像头的SDK,只能获取到一帧帧的 H264 视频数据,不知道视频流地址,需要自己解码出图像并播放。

问题解决

编译FFmpeg

点击查看

开发环境配置

点击查看

解码H264

原始数据格式

首先看我们能获取到数据格式

public class VideoStream{
//video buffer
byte[] streamBuffer;
//pps
byte[] ppsBuffer;
//sps
byte[] spsBuffer;
//当前是I帧还是P帧
int frameType;
}


Java层代码

我们需要将从Java层取到的原始数据通过JNI传递到C层,交给FFmpeg解析。 Java 类大致如下:

public class H264FrameRender {

static {
//加载自己的 so 库
System.loadLibrary("decoder");
}
//保存C中的对象内存地址
private long nativeObject;
public H264FrameRender() {
long address = this._init();
if (address <= 0) {
throw new IllegalStateException("init failed");
}
this.nativeObject = address;
}

/**
* 将当前的buffer写入缓冲队列,等待解析
*/
public void write(VideoStream videoStream) {
if (nativeObject <= 0) {
throw new IllegalStateException("H264FrameRender init failed,cannot decode ");
}
byte[] streamBuffer = videoStream.getmStreamBuffer();
byte[] spsBuffer = videoStream.getmSPSBuffer();
byte[] ppsBuffer = videoStream.getmPPSBuffer();
int spsLen = spsBuffer == null ? 0 : spsBuffer.length;
int ppsLen = ppsBuffer == null ? 0 : ppsBuffer.length;
boolean isIFrame = videoStream.getmType() == VideoStream.IFrameType;

_write(nativeObject, streamBuffer, streamBuffer.length, spsBuffer, spsLen, ppsBuffer,
ppsLen, isIFrame);
}

/**
* 释放内存,调用该方法后,将会释放C分配的内存空间,并将该Java对象标记为不可用
*/
public void release() {
if (this.nativeObject > 0) {
_release(this.nativeObject);
this.nativeObject = 0;
}
}

/**
* 如果没有主动release,C 申请的空间将在Java类销毁时自动释放
*
* @throws Throwable
*/
@Override
protected void finalize() throws Throwable {
release();
super.finalize();
}
/**
* 初始化内存空间,会调用C申请一段内存,并返回内存地址
*/
private native long _init();

/**
* 将数据通过 JNI 交给 C 去解析
*/
private native void _write(long nativeObject, byte[] streamBuffer, int length, byte[] spsBuffer,
int spsLen, byte[] ppsBuffer, int ppsLen, boolean isIFrame);

}


JNI 代码

新建 h264_render.ch264_render.h ,添加两个JNI方法:

//h264_render.h
#ifndef LITTLELF_JNI_H264_RENDER_H
#define LITTLELF_JNI_H264_RENDER_H

#include <jni.h>

//Java: _init()
JNIEXPORT jlong JNICALL
Java_com_hencenx_littlelf_jni_H264FrameRender__1init(JNIEnv *env, jobject instance);

//Java: _write(long nativeObject, long nativeObject, byte[] streamBuffer, int length, byte[] spsBuffer,int spsLen, byte[] ppsBuffer, int ppsLen, boolean isIFrame)
JNIEXPORT void JNICALL
Java_com_hencenx_littlelf_jni_H264FrameRender__1write(JNIEnv *env, jobject instance,
jlong nativeObject, jbyteArray streamBuffer_,
jint length, jbyteArray spsBuffer_,
jint spsLen, jbyteArray ppsBuffer_,
jint ppsLen, jboolean isIFrame);
//Java: _release(long nativeObject)
JNIEXPORT void JNICALL
Java_com_hencenx_littlelf_jni_H264FrameRender__1release(JNIEnv *env, jobject instance,
jlong nativeObject);

#endif //LITTLELF_JNI_H264_RENDER_H


至此,我们可以在 C 层访问到流的 buffer 了。

FFmpeg 解析视频流

解码基本流程

FFmpeg 提供了一个解码和编码的 demo,代码很简单,精简后的代码如下:

//1.注册所有编解码器,注册后才能使用
avcodec_register_all();
//2.从注册的解码器里找到H264解码器
AVCodec * codec = avcodec_find_decoder(AV_CODEC_ID_H264);
//3. 初始化解码的上下文,上下文很关键,包含了解码所需要的信息
AVCodecContext * ctx = avcodec_alloc_context3(codec);
//准备一个容器用来装需要解码的原始H264数据
AVPacket avpkt;
//4. 准备一个容器用来装解码后的数据,AVFrame既可以表示视频数据,也可以表示音频数据
AVFrame frame = av_frame_alloc();
//5. 初始化avpkt,并将H264数据放进去(此处代码省略)
//6. 初始化解码上下文,设置视频宽高等(因为可能是从I帧中获取的,所以写在这一步,此处代码省略)
//7. 根据解码上下文打开解码器,这样解码器才算初始完毕,可以解码了
avcodec_open(ctx, codec, NULL);
//8. 解码 - 发送需要解码的数据给上下文
avcodec_send_packet(ctx, avpkt);
//9. 解码 - 从上下文中获取解码后的frame,解码完成
avcodec_receive_frame(ctx, frame);


利用 sps 和 pps 初始化 上下文

我们的处理流程和 demo 类似,但稍有不同。因为解码H264必须提供视频的宽高,否则解析过程就会报错。 但是原始数据中并没有提供视频宽高。经过查阅得知,sps 和 pps 中就包含了视频宽高和其他一些解码必须的数据。我们需要将 sps 和 pps 放到AVCodecContext 的 extradata 中。

代码如下:

int extra_len = sps_len + pps_len;
ctx->extradata = av_malloc(
sizeof(uint8_t) * (size_t) (extra_len + AV_INPUT_BUFFER_PADDING_SIZE));
ctx->extradata_size = extra_len;
memcpy(ctx->extradata, (uint8_t *) input_sps, (size_t) sps_len);
memcpy(ctx->extradata + (size_t) sps_len, (uint8_t *) input_pps, (size_t) pps_len);


经过以上处理,H264 解码就完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: