android ffmpeg軟,硬解碼實現(ffmpeg 3.3.4)
阿新 • • 發佈:2019-02-20
前提:編譯出ffmpeg.so庫檔案,或者從某處得到可用so,可依照上一篇配置檔案進行配置,裁剪編譯。
1 軟解碼實現:
JNIEXPORT int JNICALL Java_h264_Native_PlayLocalVideo(JNIEnv *env, jobject obj,jstring inputFilePath_,jobject surface) {
const char *path = env->GetStringUTFChars(inputFilePath_, 0);
av_log_set_callback(ffmpeg_android_log_callback);
av_register_all();
int ret;
AVFormatContext * fmt_ctx = avformat_alloc_context();
if (avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) {
LOGD("can not open file");
return -1;
}
ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env,surface);
if (nativeWindow == NULL) {
LOGD("ANativeWindow_fromSurface error" );
return -3;
}
//繪製時候的緩衝區
ANativeWindow_Buffer outBuffer;
//獲取視訊流解碼器
AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);
codec_ctx->width = 1280;
codec_ctx->height = 720;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
AVCodec *avCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
LOGD("mcodec is %d \n " ,avCodec);
//開啟解碼器
if ((ret = avcodec_open2(codec_ctx, avCodec, NULL)) < 0) {
ret = -3;
return -4;
}
//迴圈從檔案讀取一幀壓縮資料
//開始讀取視訊
int y_size = codec_ctx->width * codec_ctx->height;
AVPacket *pkt = (AVPacket *) malloc(sizeof(AVPacket));//分配一個packet
av_new_packet(pkt, y_size);//分配packet的資料
AVFrame *yuvFrame = av_frame_alloc();
AVFrame *rgbFrame = av_frame_alloc();
// 顏色轉換器
struct SwsContext *m_swsCtx = sws_getContext(codec_ctx->width, codec_ctx->height,
codec_ctx->pix_fmt, codec_ctx->width,
codec_ctx->height, AV_PIX_FMT_RGBA, SWS_BICUBIC,
NULL, NULL, NULL);
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, codec_ctx->width, codec_ctx->height, 1);
uint8_t *out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
LOGD("開始解碼");
int index = 0;
while (1) {
if (av_read_frame(fmt_ctx, pkt) < 0) {
//這裡就認為視訊讀完了
break;
}
FILE * pFile;
pFile = fopen("/sdcard/h264.txt", "wb");
if(pFile != NULL){
fwrite (pkt->data ,1,pkt->size, pFile);
}
LOGD("avcodec_send_packet index is = %d size=%d",index,pkt->size);
ret = avcodec_send_packet(codec_ctx, pkt);
if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {
LOGD("avcodec_send_packet ret=%d", ret);
av_packet_unref(pkt);
continue;
}
//從解碼器返回解碼輸出資料
ret = avcodec_receive_frame(codec_ctx, yuvFrame);
if (ret < 0 && ret != AVERROR_EOF) {
LOGD("avcodec_receive_frame ret=%d", ret);
av_packet_unref(pkt);
continue;
}
LOGD("frame pkt dts is %lld", yuvFrame->pkt_dts);
LOGD("frame pkt pts is %lld", yuvFrame->pkt_pts);
LOGD("frame pkt is %lld", yuvFrame->pts);
sws_scale(m_swsCtx, (const uint8_t *const *) yuvFrame->data, yuvFrame->linesize, 0,codec_ctx->height, rgbFrame->data, rgbFrame->linesize);
//設定緩衝區的屬性
ANativeWindow_setBuffersGeometry(nativeWindow, codec_ctx->width, codec_ctx->height, WINDOW_FORMAT_RGBA_8888);
ret = ANativeWindow_lock(nativeWindow, &outBuffer, NULL);
if (ret != 0) {
LOGD("ANativeWindow_lock error");
return -5;
}
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize,
(const uint8_t *) outBuffer.bits, AV_PIX_FMT_RGBA,
codec_ctx->width, codec_ctx->height, 1);
//fill_ANativeWindow(&outBuffer,outBuffer.bits,rgbFrame);
// 將緩衝區資料顯示到surfaceView
ret = ANativeWindow_unlockAndPost(nativeWindow);
if (ret != 0) {
LOGD("ANativeWindow_unlockAndPost error");
return -6;
}
LOGD("成功顯示到緩衝區%d次", ++index);
av_packet_unref(pkt);
usleep(150000);//stop 1/6 second
}
av_free(out_buffer);
av_frame_free(&rgbFrame);
avcodec_close(codec_ctx);
sws_freeContext(m_swsCtx);
avformat_close_input(&fmt_ctx);
env->ReleaseStringUTFChars(inputFilePath_, path);
LOGD("解析完成");
return 1;
}
2 硬解碼實現
1:修改ffmpeg配置檔案,開啟硬解碼選項,重新編譯
–enable-jni \
–enable-mediacodec \
–enable-decoder=h264_mediacodec \
–enable-hwaccel=h264_mediacodec \
2:修改解碼器獲取方式
codec = avcodec_find_decoder_by_name(“h264_mediacodec”);
總結:
ffmpeg簡單使用總是這樣,其中編譯流程可謂艱難困苦,前後編譯嘗試上百次。開啟neno,開啟硬解碼。都不是容易的事情。自己也是琢磨了三個月的空餘時間,才走完軟硬解碼流程。其中為完成單幀硬解碼需求涉及原始碼的修改也是有,不過很少。在出現呼叫問題的情況下,出現問題的解決辦法,檢視原始碼是最快的。我的配置應該還是可行的,不過ubuntu版本問題,ndk版本問題,ffmpeg版本問題,所以覺知此時要躬行,我能給的幫助是,android ffmpeg硬解碼可行,可多路,多執行緒同時解碼,如果只是單路視訊的播放,大可不必費這個心思,直接採用android硬解碼也是可行的,ffmpeg實現其實也是反射java的mediacodec實現。在實際的商業專案中使用,開啟neno加速,開啟多執行緒解碼,能使解碼效率大大提高。