一磺陡、java層創(chuàng)建一個(gè)SuifaceView,獲取其holder對(duì)象舒萎,定義一個(gè)native函數(shù)將該holder傳遞給jni層進(jìn)行繪制曹洽。
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() {
//獲取holder對(duì)象
SurfaceHolder holder = getHolder();
holder.setFormat(PixelFormat.RGBA_8888);//設(shè)置格式
}
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() {
//將holder傳遞給底層進(jìn)行繪制
render(input, VideoView.this.getHolder().getSurface());
}
}).start();
}
public native void render(String input, Surface surface);
}
二鳍置、使用ffmpeg解碼視頻并使用native繪制在SurfaceView的holder對(duì)象上
extern "C" {
//編碼
#include "libavcodec/avcodec.h"
//封裝格式處理
#include "libavformat/avformat.h"
//像素處理
#include "libswscale/swscale.h"
//native_window_jni 在ndk 的libandroid.so庫中,需要在CMakeLists.txt中引入android庫
#include <android/native_window_jni.h>
#include <unistd.h>//sleep用的頭文件
}
/**
*將任意格式的視頻在手機(jī)上進(jìn)行播放送淆,使用native進(jìn)行繪制
* env:虛擬機(jī)指針
* inputStr:視頻文件路徑
* surface: 從java層傳遞過來的SurfaceView的serface對(duì)象
*/
void ffmpegVideoPlayer(JNIEnv *env, char *inputStr, jobject surface) {
// 1.注冊(cè)各大組件税产,執(zhí)行ffmgpe都必須調(diào)用此函數(shù)
av_register_all();
//2.得到一個(gè)ffmpeg的上下文(上下文里面封裝了視頻的比特率,分辨率等等信息...非常重要)
AVFormatContext *pContext = avformat_alloc_context();
//3.打開一個(gè)視頻
if (avformat_open_input(&pContext, inputStr, NULL, NULL) < 0) {
LOGE("打開失敗");
return;
}
//4.獲取視頻信息(將視頻信息封裝到上下文中)
if (avformat_find_stream_info(pContext, NULL) < 0) {
LOGE("獲取信息失敗");
return;
}
//5.用來記住視頻流的索引
int vedio_stream_idx = -1;
//從上下文中尋找找到視頻流
for (int i = 0; i < pContext->nb_streams; ++i) {
LOGE("循環(huán) %d", i);
//codec:每一個(gè)流 對(duì)應(yīng)的解碼上下文
//codec_type:流的類型
if (pContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
//如果找到的流類型 == AVMEDIA_TYPE_VIDEO 即視頻流坊夫,就將其索引保存下來
vedio_stream_idx = i;
}
}
//獲取到解碼器上下文
AVCodecContext *pCodecCtx = pContext->streams[vedio_stream_idx]->codec;
//獲取解碼器(加密視頻就是在此處無法獲茸┑凇)
AVCodec *pCodex = avcodec_find_decoder(pCodecCtx->codec_id);
LOGE("獲取視頻編碼 %p", pCodex);
//6.打開解碼器撤卢。 (ffempg版本升級(jí)名字叫做avcodec_open2)
if (avcodec_open2(pCodecCtx, pCodex, NULL) < 0) {
LOGE("解碼失敗");
return;
}
//----------------------解碼前準(zhǔn)備--------------------------------------
//準(zhǔn)備開始解碼時(shí)需要一個(gè)AVPacket存儲(chǔ)數(shù)據(jù)(通過av_malloc分配內(nèi)存)
AVPacket *packet = (AVPacket *) av_malloc(sizeof(AVPacket));
av_init_packet(packet);//初始化結(jié)構(gòu)體
//解封裝需要AVFrame
AVFrame *frame = av_frame_alloc();
//聲明一個(gè)rgb_Frame的緩沖區(qū)
AVFrame *rgb_Frame = av_frame_alloc();
//rgb_Frame 的緩沖區(qū) 初始化
uint8_t *out_buffer = (uint8_t *) av_malloc(
avpicture_get_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height));
//給緩沖區(qū)進(jìn)行替換
int re = avpicture_fill((AVPicture *) rgb_Frame, out_buffer, AV_PIX_FMT_RGBA, pCodecCtx->width,
pCodecCtx->height);
LOGE("寬 %d 高 %d", pCodecCtx->width, pCodecCtx->height);
//格式轉(zhuǎn)碼需要的轉(zhuǎn)換上下文(根據(jù)封裝格式的寬高和編碼格式环凿,以及需要得到的格式的寬高)
//pCodecCtx->pix_fmt 封裝格式文件的上下文
//AV_PIX_FMT_RGBA : 目標(biāo)格式 需要跟SurfaceView設(shè)定的格式相同
//SWS_BICUBIC :清晰度稍微低一點(diǎn)的算法(轉(zhuǎn)換算法,前面的算法清晰度高效率低放吩,下面的算法清晰度低效率高)
//NULL,NULL,NULL : 過濾器等
SwsContext *swsContext = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGBA,
SWS_BICUBIC, NULL, NULL, NULL
);
int frameCount = 0;
//獲取nativeWindow對(duì)象,準(zhǔn)備進(jìn)行繪制
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
ANativeWindow_Buffer outBuffer;//申明一塊緩沖區(qū) 用于繪制
//------------------------一楨一幀開始解碼--------------------
int length = 0;
int got_frame;
while (av_read_frame(pContext, packet) >= 0) {//開始讀每一幀的數(shù)據(jù)
if (packet->stream_index == vedio_stream_idx) {//如果這是一個(gè)視頻流
//7.解封裝(將packet解壓給frame智听,即:拿到了視頻數(shù)據(jù)frame)
length = avcodec_decode_video2(pCodecCtx, frame, &got_frame, packet);//解封裝函數(shù)
LOGE(" 獲得長(zhǎng)度 %d 解碼%d ", length, frameCount++);
if (got_frame > 0) {
//8.準(zhǔn)備繪制
//配置繪制信息 寬高 格式(這個(gè)繪制的寬高直接決定了視頻在屏幕上顯示的情況,這樣會(huì)平鋪整個(gè)屏幕渡紫,可以根據(jù)特定的屏幕分辨率和視頻寬高進(jìn)行匹配)
ANativeWindow_setBuffersGeometry(nativeWindow, pCodecCtx->width, pCodecCtx->height,
WINDOW_FORMAT_RGBA_8888);
ANativeWindow_lock(nativeWindow, &outBuffer, NULL);//鎖定畫布(outBuffer中將會(huì)得到數(shù)據(jù))
//9.轉(zhuǎn)碼(轉(zhuǎn)碼上下文到推,原數(shù)據(jù),一行數(shù)據(jù)惕澎,開始位置莉测,yuv的緩沖數(shù)組,yuv一行的數(shù)據(jù))
sws_scale(swsContext, (const uint8_t *const *) frame->data, frame->linesize, 0,
frame->height, rgb_Frame->data,
rgb_Frame->linesize
);
//10.繪制
uint8_t *dst = (uint8_t *) outBuffer.bits; //實(shí)際的位數(shù)
int destStride = outBuffer.stride * 4; //拿到一行有多少個(gè)字節(jié) RGBA
uint8_t *src = (uint8_t *) rgb_Frame->data[0];//像素?cái)?shù)據(jù)的首地址
int srcStride = rgb_Frame->linesize[0]; //實(shí)際內(nèi)存一行的數(shù)量
for (int i = 0; i < pCodecCtx->height; ++i) {
//將rgb_Frame緩沖區(qū)里面的數(shù)據(jù)一行一行copy到window的緩沖區(qū)里面
//copy到window緩沖區(qū)的時(shí)候進(jìn)行一些偏移設(shè)置可以將視頻播放居中
memcpy(dst + i * destStride, src + i * srcStride, srcStride);
}
ANativeWindow_unlockAndPost(nativeWindow);//解鎖畫布
usleep(1000 * 16);//可以根據(jù)幀率休眠16ms
}
}
av_free_packet(packet);//釋放
}
ANativeWindow_release(nativeWindow);//釋放window
av_frame_free(&frame);
av_frame_free(&rgb_Frame);
avcodec_close(pCodecCtx);
avformat_free_context(pContext);
free(inputStr);
}