要实现的功能:我们将视频文件(格式或者是mp4、flv、rmvb,这些都是压缩后的格式)解码成RGBA之后,利用SurfaceView来进行播放。
cpp文件
#include
#include
#include
extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
//像素处理
#include "libswscale/swscale.h"
#include
#include
}
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"jason",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"jason",FORMAT,##__VA_ARGS__);
extern "C"
JNIEXPORT void JNICALL
Java_com_dongnao_ffmpegdemo_VideoView_render(JNIEnv *env, jobject instance, jstring input_,
jobject surface) {
const char *input = env->GetStringUTFChars(input_,false);
av_register_all();
//Allocate an AVFormatContext.
AVFormatContext *pFormatCtx = avformat_alloc_context();
//第四个参数是 可以传一个 字典 是一个入参出参对象 0 on success
if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0) {
LOGE("%s","打开输入视频文件失败");
}
//获取视频信息 >=0 if OK
if(avformat_find_stream_info(pFormatCtx,NULL) < 0){
LOGE("%s","获取视频信息失败");
return;
}
int vidio_stream_idx=-1;
//nb_streams:视音频流的个数
//一般情况有2个流即nb_streams=2,s->streams[0]为 视频流,s->streams[1]为音频流
for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
LOGE(" 找到视频id %d", pFormatCtx->streams[i]->codec->codec_type);
vidio_stream_idx=i;
break;
}
}
//获取视频编解码器
AVCodecContext *pCodecCtx=pFormatCtx->streams[vidio_stream_idx]->codec;
LOGE("获取视频编码器上下文 %p ",pCodecCtx);
//加密的用不了
AVCodec *pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
LOGE("获取视频编码 %p",pCodex);
//zero on success, a negative value on error
//该函数用于初始化一个视音频编解码器的AVCodecContext
//利用给定的AVCodec结构初始化AVCodecContext结构
if (avcodec_open2(pCodecCtx, pCodex, NULL)<0) {
return;
}
//AVPacket结构体中存放的是压缩数据
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
//AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV,RGB,对音频来说是PCM)
AVFrame *frame;
frame = av_frame_alloc();
//RGB
AVFrame *rgb_frame = av_frame_alloc();
//只有指定了AVFrame的像素格式、画面大小才能真正分配内存
//给缓冲区分配内存
uint8_t *out_buffer= (uint8_t *) av_malloc(avpicture_get_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height));
LOGE("宽 %d, 高 %d ",pCodecCtx->width,pCodecCtx->height);
//设置rgbFrame的缓冲区,像素格式
//avpicture_fill函数将out_buffer指向的数据填充到rgb_frame内,但并没有拷贝,只是将rgb_frame结构内的data指针指向了out_buffer的数据
int re= avpicture_fill((AVPicture *) rgb_frame, out_buffer, AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height);
LOGE("申请内存%d ",re);
int length=0;
int got_frame;
int frameCount=0;
//SwsContext结构体主要用于视频图像的转换
//SwrContext结构体主要用于音频重采样,比如采样率转换,声道转换
SwsContext *swsContext = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,
pCodecCtx->width,pCodecCtx->height,AV_PIX_FMT_RGBA,SWS_BICUBIC,NULL,NULL,NULL
);
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
//视频缓冲区
ANativeWindow_Buffer outBuffer;
//0 if OK, < 0 on error or end of file
while (av_read_frame(pFormatCtx, packet)>=0) {
if (packet->stream_index == vidio_stream_idx) {
//看下第三个参数:@param[in,out] got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero.
//这是个"入参出参" 参数
length = avcodec_decode_video2(pCodecCtx, frame, &got_frame, packet);
LOGE(" 获得长度 %d ", length);
if (got_frame) {
//绘制之前 配置一些信息 比如宽高 格式
ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height,
WINDOW_FORMAT_RGBA_8888);
//绘制
ANativeWindow_lock(nativeWindow, &outBuffer, NULL);
LOGI("解码%d帧",frameCount++);
sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0
, pCodecCtx->height, rgb_frame->data,
rgb_frame->linesize);
//rgb_frame是有画面数据
uint8_t *dst= (uint8_t *) outBuffer.bits;
//拿到一行有多少个字节 RGBA
int destStride=outBuffer.stride*4;
//像素数据的首地址
//AVFrame 中对于packed格式的数据(例如RGB24),会存到data[0]里面。
//对于planar格式的数据(例如YUV420P),则会分开成data[0],data[1],data[2]...(YUV420P中data[0]存Y,data[1]存U,data[2]存V)
uint8_t * src= (uint8_t *) rgb_frame->data[0];
//实际内存一行字节数量
//For video, size in bytes of each picture line.
//For audio, size in bytes of each plane.
int srcStride = rgb_frame->linesize[0];
for (int i = 0; i < pCodecCtx->height; ++i) {
// memcpy(void *dest, const void *src, size_t n) 内存拷贝
memcpy(dst + i * destStride, src + i * srcStride, srcStride);
}
ANativeWindow_unlockAndPost(nativeWindow);
usleep(1000 * 16);
}
}
av_free_packet(packet);
}
ANativeWindow_release(nativeWindow);
av_frame_free(&frame);
avcodec_close(pCodecCtx);
avformat_free_context(pFormatCtx);
env->ReleaseStringUTFChars(input_, input);
}
VideoView
package com.dongnao.ffmpegdemo;
import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/**
* Created by david on 2017/9/20.
*/
public class VideoView extends SurfaceView {
static{
System.loadLibrary("avcodec-56");
System.loadLibrary("avdevice-56");
System.loadLibrary("avfilter-5");
System.loadLibrary("avformat-56");
System.loadLibrary("avutil-54");
System.loadLibrary("postproc-53");
System.loadLibrary("swresample-1");
System.loadLibrary("swscale-3");
System.loadLibrary("native-lib");
}
public VideoView(Context context) {
super(context);
}
public VideoView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
SurfaceHolder holder = getHolder();
holder.setFormat(PixelFormat.RGBA_8888);
}
public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void player(final String input) {
new Thread(new Runnable() {
@Override
public void run() {
//绘制功能 不需要交给SurfaveView
//VideoView.this.getHolder().getSurface()
render(input, VideoView.this.getHolder().getSurface());
}
}).start();
}
public native void render(String input, Surface surface);
}