前提:编译出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加速,开启多线程解码,能使解码效率大大提高。