在上篇文章中講到了FFmpeg解碼音頻,對于FFmpeg解碼視頻與音頻解碼流程大致相同。其區(qū)別在于播放的方式與邏輯。我們可以利用c++面向?qū)ο蟮乃枷氤殡x出來很多工作流程相同的代碼。對于音頻來說重采樣和播放,對于視頻來說將數(shù)據(jù)轉(zhuǎn)換縮放等渲染有特定邏輯。這里我們采用的是Android原生SurfaceView渲染畫面
extern "C"
JNIEXPORT void JNICALL
Java_com_youyangbo_media_FFplayer_nStart(JNIEnv *env, jobject instance,jstring _url,jobject surface) {
const char *url = env->GetStringUTFChars(url_, 0);
av_register_all();
avformat_network_init();
AVFormatContext *pFormatContext = avformat_alloc_context();
int open_result = avformat_open_input(&pFormatContext, url, NULL, NULL);
LOGE("url = %s", url);
if (open_result != 0) {
LOGE("打開媒體文件失敗");
return;
}
int find_stream_result = avformat_find_stream_info(pFormatContext, NULL);
if (find_stream_result < 0) {
LOGE("查找流信息失敗");
return;
}
int video_index = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
AVCodecParameters *pCodecParameters = pFormatContext->streams[video_index]->codecpar;
AVCodec *pCodec = avcodec_find_decoder(
pFormatContext->streams[video_index]->codecpar->codec_id);
AVCodecContext *pCodecContext = avcodec_alloc_context3(pCodec);
int avcodec_parameters_to_context_result = avcodec_parameters_to_context(
pCodecContext,
pCodecParameters);
int open_codec_result = avcodec_open2(pCodecContext, pCodec, NULL);
if (open_codec_result != 0) {
LOGE("打開解碼器失敗");
return;
}
AVFrame * pRgbaFrame = av_frame_alloc();
int frameSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecContext->width,
pCodecContext->height, 1);
uint8_t * pFrameBuffer = (uint8_t *) malloc(frameSize);
av_image_fill_arrays(pRgbaFrame->data, pRgbaFrame->linesize, pFrameBuffer, AV_PIX_FMT_RGBA,
pCodecContext->width, pCodecContext->height, 1);
// 1. 獲取窗體
ANativeWindow *pNativeWindow = ANativeWindow_fromSurface(env, surface);
// 2. 設(shè)置緩存區(qū)的數(shù)據(jù)
ANativeWindow_setBuffersGeometry(pNativeWindow, pCodecContext->width,
pCodecContext->height, WINDOW_FORMAT_RGBA_8888);
// Window 緩沖區(qū)的 Buffer
ANativeWindow_Buffer outBuffer;
//這里最好定義變量
while (true) {
AVPacket *pkt = av_packet_alloc();
if (av_read_frame(pFormatContext, pkt) == 0) {
if (pkt->stream_index == video_index ) {
if (avcodec_send_packet(pCodecContext , pkt) == 0) {
AVFrame *pFrame = av_frame_alloc();
if (avcodec_receive_frame(pCodecContext , pFrame) == 0) {
//解出Frame數(shù)據(jù)
ANativeWindow_lock(pNativeWindow, &outBuffer, NULL);
libyuv::I420ToARGB(pFrame->data[0],
pFrame->linesize[0],
pFrame->data[2],
pFrame->linesize[2],
pFrame->data[1],
pFrame->linesize[1],
pRgbaFrame->data[0],
pRgbaFrame->linesize[0],
pCodecContext->width,
pCodecContext->height);
// 獲取stride
int dstStride = outBuffer.stride * 4;
uint8_t *src = (pRgbaFrame->data[0]);
int srcStride = pRgbaFrame->linesize[0];
// 由于window的stride和幀的stride不同,因此需要逐行復(fù)制
int h;
for (h = 0; h < pCodecContext->height; h++) {
memcpy((u_int8_t*)outBuffer.bits + h * dstStride, src + h * srcStride, srcStride);
}
// 把數(shù)據(jù)推到緩沖區(qū)
ANativeWindow_unlockAndPost(pNativeWindow);
av_frame_unref(pFrame);
av_frame_free(&pFrame);
av_packet_unref(pkt);
av_packet_free(&pkt);
} else {
av_frame_unref(pFrame);
av_frame_free(&pFrame);
av_packet_unref(pkt);
av_packet_free(&pkt);
}
} else {
av_packet_unref(pkt);
av_packet_free(&pkt);
}
}
} else {
av_packet_unref(pkt);
av_packet_free(&pkt);
break;
}
}
env->ReleaseStringUTFChars(url_, url);
}
上述代碼中引用了libyuv庫,libyuv是Google開源的實現(xiàn)各種YUV與RGB之間相互轉(zhuǎn)換、旋轉(zhuǎn)、縮放的庫。簡單的介紹一下libyuv庫的編譯。
libyuv:源碼 https://github.com/lemenkov/libyuv
libyuv作為一個NDK項目,必須滿足有jni文件夾 與 Application.mk文件,所以我們新建jni目錄將libyuv 的源碼放入其下,在Android.mk文件同級目錄下創(chuàng)建Application.mk文件
#Application.mk 文件內(nèi)容
APP_ABI := armeabi x86
APP_PLATFORM := android-16
APP_ALLOW_MISSING_DEPS=true
APP_STL := stlport_static
APP_CPPFLAGS += -fno-rtti
然后直接敲ndk-build命令就可以完成libyuv的編譯。在jni目錄下會生成libs目錄包含我們所需動態(tài)庫。
編譯的過程中可能會報一些關(guān)于JPEG錯,將Android.mk文件中JPEG部分注掉就可以成功編譯
# Android.mk文件
# This is the Android makefile for libyuv for NDK.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
.............省略.............
common_CFLAGS := -Wall -fexceptions
#ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
#LOCAL_SRC_FILES += \
# source/convert_jpeg.cc \
# source/mjpeg_decoder.cc \
# source/mjpeg_validate.cc
#common_CFLAGS += -DHAVE_JPEG
#LOCAL_SHARED_LIBRARIES := libjpeg
#endif
LOCAL_CFLAGS += $(common_CFLAGS)
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_MODULE := libyuv_static
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_WHOLE_STATIC_LIBRARIES := libyuv_static
LOCAL_MODULE := libyuv
#ifneq ($(LIBYUV_DISABLE_JPEG), "yes")
#LOCAL_SHARED_LIBRARIES := libjpeg
#endif
include $(BUILD_SHARED_LIBRARY)
.............省略.............