视频解码、像素格式转换与Native原生绘制
一、视频解码
1、FFmpeg 库简介
FFmpeg 一共包含8个库
- avcodec:编解码(最重要的库)
- avformat:封装格式处理
- avfilter:滤镜特效处理
- avdevice:各自设备的输入输出
- avutil:工具库(大部分库都需要这个库的支持)
- postproc:后加工
- swresample:音频采样数据格式转换
- swscale:视频像素数据格式转换
2、FFmpeg 解码的流程图
FFmpeg 解码的流程图3、FFmpeg 数据格式简介
- AVFormatContext:封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息
- AVInputFormat:每种封装格式(例如FLV、MKV、MP4、AVI)对应一个该结构体。
- AVStream:视频文件中每个视频(音频)流对应一个该结构体
- AVCodeContext:编解码器上下文结构体,保存了视频(音频)编解码相关信息。
- AVCodec:每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体
- AVPacket:存储一帧压缩编码数据
- AVFrame:存储一帧解码后的像素(采样)数据
二、像素格式转换
像素格式转换就是将yuv420p 的转成ARGB,可以使用 C/C++ 库 libyuv 来进行转换
I420ToARGB
参数按顺序如下:
参数 | 类型 | 说明 |
---|---|---|
src_y | uint8_t* | 来源的frame的 y 帧数据 yuv_frame->data |
src_stride_y | int | 来源的frame的y大小数据 yuv_frame->linesize |
src_u | uint8_t* | 来源的frame的 u 帧数据yuv_frame->data |
src_stride_u | int | 来源的frame的u大小数据 yuv_frame->linesize |
src_v | uint8_t* | 来源的frame的 v 帧数据yuv_frame->data |
src_stride_v | int | 来源的frame的v大小数据 yuv_frame->linesize |
dst_argb | uint8_t* | 转换后的 rgb 的frame数据 |
dst_stride_argb | int | 转换后的 rgb 的frame的大小数据 |
width | int | 像素数据宽度 |
height | int | 像素数据高度 |
三、Native 原生绘制
Native 原生绘制是使用ANativeWindow 将surface 和 缓冲区buffer绑定,进而去更新缓冲区的数据,并刷新到 surface 就可以实现原生绘制
1、获取ANativeWindow指针,定义缓冲区
//Native 绘制
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
//缓冲区buffer
ANativeWindow_Buffer windowBuffer;
2、设置缓冲区参数
//设置缓冲区参数
ANativeWindow_setBuffersGeometry(nativeWindow, avCodecContext->width,
avCodecContext->height, WINDOW_FORMAT_RGBA_8888);
3、刷新数据到缓冲区
- 对缓冲区进行加锁
- 刷新转换后的数据到缓冲区
- 对缓冲区数据进行解锁
//LOCK
ANativeWindow_lock(nativeWindo&windowBuffer, NULL
avpicture_fill((AVPicture rgba_frame, windowBuffer.bitPIX_FMT_RGBA,
avCodecContext->width,
avCodecContext->height
//fix buffer
I420ToARGB(
yuv_frame->data[0yuv_frame->linesize[0],
yuv_frame->data[2yuv_frame->linesize[2],
yuv_frame->data[1yuv_frame->linesize[1],
rgba_frame->data[0rgba_frame->linesize[0],
avCodecContext->widtavCodecContext->height
//UNLOCK
ANativeWindow_unlockAndPost(natiindow);
4、释放nativeWindow
ANativeWindow_release(nativeWindow);
四、使用FFmpeg实现native原生绘制,显示视频图像
之前说明了Android Studio 使用 CMake 来配置FFmpeg 的方法,这里就省略项目配置与Java代码部分,主要来实现C/C++代码部分,将像素数据会知道 Surface 上
#include "cn_onestravel_ndk_ffmpeg_render_VideoPlayer.h"
#include <android/log.h>
#include <unistd.h>
//编码
#include "include/libavcodec/avcodec.h"
//封装格式处理
#include "include/libavformat/avformat.h"
//像素处理
#include "include/libswscale/swscale.h"
#include "include/libavutil/avutil.h"
#include "include/libavutil/frame.h"
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <libyuv.h>
#include <pthread.h>
#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"FFMPEG",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"FFMPEG",FORMAT,##__VA_ARGS__);
/*
* Class: cn_onestravel_ndk_ffmpeg_render_VideoPlayer
* Method: render
* Signature: (Ljava/lang/String;Landroid/view/Surface;)V
*/
JNIEXPORT void JNICALL Java_cn_onestravel_ndk_ffmpeg_render_VideoPlayer_render
(JNIEnv *env, jclass jcls, jstring jstr_input, jobject surface) {
const char *cstr_input = (*env)->GetStringUTFChars(env, jstr_input, NULL);
//注册ffmpeg 所有组件
av_register_all();
//封装格式上下文
AVFormatContext *formatContext = avformat_alloc_context();
//打开输入视频文件
if (avformat_open_input(&formatContext, cstr_input, NULL, NULL) != 0) {
LOGE("无法打开视频文件");
return;
}
//获取视频文件信息
if (avformat_find_stream_info(formatContext, NULL) < 0) {
LOGE("获取视频文件信息失败");
return;
}
//获取视频流的索引位置
//遍历所有类型的流(音频流,视频流、字幕流)
int i = 0;
int v_stream_index = -1;
for (; i < formatContext->nb_streams; i++) {
if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
v_stream_index = i;
break;
}
}
if (v_stream_index < 0) {
LOGE("%s", "找不到视频流\n");
return;
}
//获取视频流中的编解码的上下文
AVCodecContext *avCodecContext = formatContext->streams[v_stream_index]->codec;
//根据视频编解码上下文的id得到对应的编解码器
AVCodec *avCodec = avcodec_find_decoder(avCodecContext->codec_id);
if (avCodec == NULL) {
LOGE("未找到解码器");
return;
}
//打开解码器
if (avcodec_open2(avCodecContext, avCodec, NULL) < 0) {
LOGE("打开解码器失败");
return;
}
//输出视频信息
LOGI("视频文件格式:%s", formatContext->iformat->name);
//formatContext->duration单位为微妙
LOGI("视频时长:%lld", (formatContext->duration) / 1000000);
LOGI("视频的宽度和高度 W:%d ,H:%d", avCodecContext->width, avCodecContext->height);
LOGI("视频解码器名称:%s", avCodec->name);
//准备读取
//AVPacket用于存储一帧一帧的压缩数据(H264)
//缓冲区,开辟空间
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
AVFrame *yuv_frame = av_frame_alloc();
AVFrame *yuv_scale_frame = av_frame_alloc();
AVFrame *rgba_frame = av_frame_alloc();
//Native 绘制
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
//缓冲区buffer
ANativeWindow_Buffer windowBuffer;
int len, got_frame, frame_count = 0;
while (av_read_frame(formatContext, packet) >= 0) {
len = avcodec_decode_video2(avCodecContext, yuv_frame, &got_frame, packet);
//不为0,正在解码
if (got_frame) {
int i = frame_count++;
LOGI("解码%d帧", i);
ANativeWindow_setBuffersGeometry(nativeWindow, avCodecContext->width,
avCodecContext->height, WINDOW_FORMAT_RGBA_8888);
//LOCK
ANativeWindow_lock(nativeWindow, &windowBuffer, NULL);
avpicture_fill((AVPicture *) rgba_frame, windowBuffer.bits, PIX_FMT_RGBA,
avCodecContext->width,
avCodecContext->height);
//fix buffer
I420ToARGB(
yuv_frame->data[0], yuv_frame->linesize[0],
yuv_frame->data[2], yuv_frame->linesize[2],
yuv_frame->data[1], yuv_frame->linesize[1],
rgba_frame->data[0], rgba_frame->linesize[0],
avCodecContext->width, avCodecContext->height);
//UNLOCK
ANativeWindow_unlockAndPost(nativeWindow);
// ANativeWindow_release(nativeWindow);
usleep(16 * 1000);
}
av_free_packet(packet);
}
ANativeWindow_release(nativeWindow);
av_frame_free(yuv_frame);
av_frame_free(rgba_frame);
avcodec_close(avCodec);
avcodec_free_context(avCodecContext);
avformat_free_context(formatContext);
(*env)->ReleaseStringUTFChars(env, jstr_input, cstr_input);
}
自定义 VideoView 继承自 SurfaceView
public class VideoView extends SurfaceView {
public VideoView(Context context) {
super(context);
init();
}
public VideoView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public void init(){
//初始化SurfaceView的像素格式
SurfaceHolder holder = getHolder();
holder.setFormat(PixelFormat.RGBA_8888);
}
}
网友评论