android ffmpeg视频硬解码例子_android libvlc 硬解码-程序员宅基地

技术标签: 视频硬解码  android  ffmpeg  

android ffmpeg mediacodec 硬解码

ffmpeg 3.1以后 ffmpeg加入了硬解。
用法其实很简单,首先编译一个带硬解码的ffmpeg 库文件。

#!/bin/bash
PLATFORM=/Users/lake/test/android-ndk-r14b/platforms/android-19/arch-arm/
TOOLCHAIN=/Users/lake/test/android-ndk-r14b/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
PREFIX=./android
function build_one
{
./configure \
--prefix=$PREFIX \
--target-os=android \
--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
--arch=arm \
--sysroot=$PLATFORM \
--extra-cflags="-I$PLATFORM/usr/include" \
--cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \
--nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \
--disable-shared \
--disable-ffmpeg \
--disable-ffplay \
--disable-ffprobe \
--disable-ffserver \
--disable-doc \
--disable-symver \
--enable-small \
--enable-gpl \
--enable-asm \
--enable-jni \
--enable-mediacodec \
--enable-decoder=h264_mediacodec \
--enable-hwaccel=h264_mediacodec \
--enable-decoder=hevc_mediacodec \
--enable-decoder=mpeg4_mediacodec \
--enable-decoder=vp8_mediacodec \
--enable-decoder=vp9_mediacodec \
--enable-nonfree \
--enable-version3 \
--extra-cflags="-Os -fpic $ADDI_CFLAGS" \
--extra-ldflags="$ADDI_LDFLAGS" \
$ADDITIONAL_CONFIGURE_FLAG


    make clean
    make j8
    make install


$TOOLCHAIN/bin/arm-linux-androideabi-ld \
-rpath-link=$PLATFORM/usr/lib \
-L$PLATFORM/usr/lib \
-L$PREFIX/lib \
-soname libffmpeg.so -shared -nostdlib -Bsymbolic --whole-archive --no-undefined -o \
$PREFIX/libffmpeg.so \
libavcodec/libavcodec.a \
libavfilter/libavfilter.a \
libswresample/libswresample.a \
libavformat/libavformat.a \
libavutil/libavutil.a \
libswscale/libswscale.a \
libavdevice/libavdevice.a \
libpostproc/libpostproc.a \
-lc -lm -lz -ldl -llog --dynamic-linker=/system/bin/linker \
$TOOLCHAIN/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a
}
# arm v7vfp
CPU=arm
OPTIMIZE_CFLAGS="-mfloat-abi=softfp -mfpu=vfp -marm -march=$CPU "
ADDI_CFLAGS="-marm"
build_one

PLATFORM和TOOLCHAIN请用自己电脑的路径
温馨提示,请不要用as自带的ndk-bundle,因为这个自带的ndk版本是不完全的,编译会出错哦。

这个编出来是单独一个ffmpeg这个so文件 有点大哈5,60mb样子

然后硬解码的代码

#include <jni.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

extern "C" {
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavcodec/jni.h>
}

#ifdef ANDROID

#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>

#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "(>_<)", format, ##__VA_ARGS__)
#define LOGI(format, ...)  __android_log_print(ANDROID_LOG_INFO,  "(^_^)", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf("(>_<) " format "\n", ##__VA_ARGS__)
#define LOGI(format, ...)  printf("(^_^) " format "\n", ##__VA_ARGS__)
#endif

extern "C"
JNIEXPORT jint JNICALL Java_com_lake_ndktest_FFmpeg_play
        (JNIEnv *env, jobject obj, jstring input_jstr, jobject surface) {
    LOGI("play");
    // sd卡中的视频文件地址,可自行修改或者通过jni传入
    const char *file_name = env->GetStringUTFChars(input_jstr, NULL);
    LOGI("file_name:%s\n", file_name);
    av_register_all();

    AVFormatContext *pFormatCtx = avformat_alloc_context();

    // Open video file
    if (avformat_open_input(&pFormatCtx, file_name, NULL, NULL) != 0) {

        LOGE("Couldn't open file:%s\n", file_name);
        return -1; // Couldn't open file
    }

    // Retrieve stream information
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGE("Couldn't find stream information.");
        return -1;
    }
    // Find the first video stream
    //找到第一个视频流,因为里面的流还有可能是音频流或者其他的,我们摄像头只关心视频流
    int videoStream = -1, i;
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
            && videoStream < 0) {
            videoStream = i;
            break;
        }
    }
    if (videoStream == -1) {
        LOGE("Didn't find a video stream or audio steam.");
        return -1; // Didn't find a video stream
    }
    LOGI("找到视频流");
    AVCodecParameters *pCodecPar = pFormatCtx->streams[videoStream]->codecpar;
    //查找解码器
    //获取一个合适的编码器pCodec find a decoder for the video stream
    //AVCodec *pCodec = avcodec_find_decoder(pCodecPar->codec_id);
    AVCodec *pCodec;
    switch (pCodecPar->codec_id){
        case AV_CODEC_ID_H264:
            pCodec = avcodec_find_decoder_by_name("h264_mediacodec");//硬解码264
            if (pCodec == NULL) {
                LOGE("Couldn't find Codec.\n");
                return -1;
            }
            break;
        case AV_CODEC_ID_MPEG4:
            pCodec = avcodec_find_decoder_by_name("mpeg4_mediacodec");//硬解码mpeg4
            if (pCodec == NULL) {
                LOGE("Couldn't find Codec.\n");
                return -1;
            }
            break;
        case AV_CODEC_ID_HEVC:
            pCodec = avcodec_find_decoder_by_name("hevc_mediacodec");//硬解码265
            if (pCodec == NULL) {
                LOGE("Couldn't find Codec.\n");
                return -1;
            }
            break;
        default:
            pCodec = avcodec_find_decoder(pCodecPar->codec_id);//软解
            if (pCodec == NULL) {
                LOGE("Couldn't find Codec.\n");
                return -1;
            }
            break;
    }

    LOGI("获取解码器");
    //打开这个编码器,pCodecCtx表示编码器上下文,里面有流数据的信息
    // Get a pointer to the codec context for the video stream
    AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);

    // Copy context
    if (avcodec_parameters_to_context(pCodecCtx, pCodecPar) != 0) {
        fprintf(stderr, "Couldn't copy codec context");
        return -1; // Error copying codec context
    }

    LOGI("视频流帧率:%d fps\n", pFormatCtx->streams[videoStream]->r_frame_rate.num /
                           pFormatCtx->streams[videoStream]->r_frame_rate.den);

    int iTotalSeconds = (int) pFormatCtx->duration / 1000000;
    int iHour = iTotalSeconds / 3600;//小时
    int iMinute = iTotalSeconds % 3600 / 60;//分钟
    int iSecond = iTotalSeconds % 60;//秒
    LOGI("持续时间:%02d:%02d:%02d\n", iHour, iMinute, iSecond);

    LOGI("视频时长:%lld微秒\n", pFormatCtx->streams[videoStream]->duration);
    LOGI("持续时间:%lld微秒\n", pFormatCtx->duration);
    LOGI("获取解码器SUCESS");
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        LOGE("Could not open codec.");
        return -1; // Could not open codec
    }
    LOGI("获取native window");
    // 获取native window
    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, surface);
    LOGI("获取视频宽高");
    // 获取视频宽高
    int videoWidth = pCodecCtx->width;
    int videoHeight = pCodecCtx->height;
    LOGI("设置native window的buffer大小,可自动拉伸");
    // 设置native window的buffer大小,可自动拉伸
    ANativeWindow_setBuffersGeometry(nativeWindow, videoWidth, videoHeight,
                                     WINDOW_FORMAT_RGBA_8888);
    ANativeWindow_Buffer windowBuffer;
    LOGI("Allocate video frame");
    // Allocate video frame
    AVFrame *pFrame = av_frame_alloc();
    LOGI("用于渲染");
    // 用于渲染
    AVFrame *pFrameRGBA = av_frame_alloc();
    if (pFrameRGBA == NULL || pFrame == NULL) {
        LOGE("Could not allocate video frame.");
        return -1;
    }
    LOGI("Determine required buffer size and allocate buffer");
    // Determine required buffer size and allocate buffer
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecCtx->width, pCodecCtx->height,
                                            1);
    uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    av_image_fill_arrays(pFrameRGBA->data, pFrameRGBA->linesize, buffer, AV_PIX_FMT_RGBA,
                         pCodecCtx->width, pCodecCtx->height, 1);
    LOGI("由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换");
    // 由于解码出来的帧格式不是RGBA的,在渲染之前需要进行格式转换
    struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width/*视频宽度*/, pCodecCtx->height/*视频高度*/,
                                                pCodecCtx->pix_fmt/*像素格式*/,
                                                pCodecCtx->width/*目标宽度*/,
                                                pCodecCtx->height/*目标高度*/, AV_PIX_FMT_RGBA/*目标格式*/,
                                                SWS_BICUBIC/*图像转换的一些算法*/, NULL, NULL, NULL);
    if (sws_ctx == NULL) {
        LOGE("Cannot initialize the conversion context!\n");
        return -1;
    }
    LOGI("格式转换成功");
    LOGE("开始播放");
    int ret;
    AVPacket packet;
    while (av_read_frame(pFormatCtx, &packet) >= 0) {
        // Is this a packet from the video stream?
        if (packet.stream_index == videoStream) {
            //该楨位置
            float timestamp = packet.pts * av_q2d(pFormatCtx->streams[videoStream]->time_base);
            LOGI("timestamp=%f", timestamp);
            // 解码
            ret = avcodec_send_packet(pCodecCtx, &packet);
            if (ret < 0) {
                break;
            }
            while (avcodec_receive_frame(pCodecCtx, pFrame) == 0) {
   //绘图
                // lock native window buffer
                ANativeWindow_lock(nativeWindow, &windowBuffer, 0);
                // 格式转换
                sws_scale(sws_ctx, (uint8_t const *const *) pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height,
                          pFrameRGBA->data, pFrameRGBA->linesize);
                // 获取stride
                uint8_t *dst = (uint8_t *) windowBuffer.bits;
                int dstStride = windowBuffer.stride * 4;
                uint8_t *src = pFrameRGBA->data[0];
                int srcStride = pFrameRGBA->linesize[0];

                // 由于window的stride和帧的stride不同,因此需要逐行复制
                int h;
                for (h = 0; h < videoHeight; h++) {
                    memcpy(dst + h * dstStride, src + h * srcStride, srcStride);
                }
                ANativeWindow_unlockAndPost(nativeWindow);
            }
        }
        av_packet_unref(&packet);
    }
    LOGE("播放完成");
    av_free(buffer);
    av_free(pFrameRGBA);

    // Free the YUV frame
    av_free(pFrame);

    // Close the codecs
    avcodec_close(pCodecCtx);

    // Close the video file
    avformat_close_input(&pFormatCtx);
    return 0;
}

jint JNI_OnLoad(JavaVM* vm, void* reserved)//这个类似android的生命周期,加载jni的时候会自己调用
{
    LOGI("ffmpeg JNI_OnLoad");
    av_jni_set_java_vm(vm, reserved);
    return JNI_VERSION_1_6;
}

软解和硬解区别太大了,尤其1080p的视频,软解很慢,硬解速度非常快,质的提升啊。所以理论上手机支持硬解的视频优先硬解,不支持再调用ffmpeg的软解。


demo例子 https://github.com/lakehubo/NDKtest/tree/ffmpeg-meidacodec

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lakebobo/article/details/79619584

智能推荐

Fairseq学习日记:注定麻烦的旅程_final_lr_scale-程序员宅基地

文章浏览阅读6.6k次,点赞10次,收藏23次。现在开头:Fairseq是一个正在快速迭代的产品,而且是开源的!这不是表扬,这意味着三件事情:1.他没有文档!所有框架代码都没有任何注释,包括函数docstring都没有2.他没有经过有效测试,估计是抢时间吧!即使是官网Readme里的例子也是无法跑起来的!3.他是一个框架,而且是一个非常不Pythonic的框架,充斥着inline/包装器/莫名其妙的语法。虽然这三点决定他真的对不住Facebook的金字招牌,但是作为一个学习者,总要把他运行起来,那么开始这场针对 FaceBOOK派“全_final_lr_scale

[Linux][Busybox]分享你不知道Top 命令参数_busybox top-程序员宅基地

文章浏览阅读5.1k次。目录摘要:基本操作与命令介绍:进入top后交互一点点新的操作Author: Keivn.Xu [email protected]摘要: 玩过Linux一定使用过busybox top命令,但下面的操作方法,你不一定有见过。基本操作与命令介绍:console:/ $ busybox top -help top: invalid optio..._busybox top

rssi参数获取_信号强度(RSSI)知识整理-程序员宅基地

文章浏览阅读1.6k次。为什么无线信号(RSSI)是负值答:其实归根到底为什么接收的无线信号是负值,这样子是不是容易理解多了。因为无线信号多为mW级别,所以对它进行了极化,转化为dBm而已,不表示信号是负的。1mW就是0dBm,小于1mW就是负数的dBm数。弄清信号强度的定义就行了:RSSI(接收信号强度)Received Signal Strength IndicatorRss=10logP,只需将接受到的信号功率P代..._c#获取低功耗设备的rssi信号强度

后端服务的雪崩效应及解决思路_接口超时时间过长导致雪崩效应-程序员宅基地

文章浏览阅读204次。1.RPC与本地调用的区别RPC远程调用,一般是跨平台、采用http协议,因为http协议底层使用socket技术,只要你的语言支持socket技术,就可以相互进行通讯。比如:java语言开发的接口,使用http协议,如此以来C#语言可以调用。本地调用:只支持java语言与java语言开发,使用虚拟机和虚拟机之间的通讯,RMI。2.雪崩效应产生的原因默认情况下只有一个线程池维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat线程池默认极限,可能会导致其他服务无法访问。3.雪_接口超时时间过长导致雪崩效应

linux操作redis_linux 连接redis-程序员宅基地

文章浏览阅读2.7w次,点赞4次,收藏35次。redis常用命令_linux 连接redis

小米手环NFC模拟加密门禁卡_小米nfc加密卡模拟不了-程序员宅基地

文章浏览阅读3.9k次。2、打开另一台小米手机的小米运动,或者Zepp life,连接手环,打开手环里面的NFC,选择“非加密卡模拟”。3、然后使用小米手环靠近上述步骤1中的小米手机,模拟小米手机的门禁卡。小米手环NFC模拟加密门禁卡会提示“此卡为加密卡,无法模拟”。注意:步骤1中的手机,必须是小米、红米手机,其它安卓手机不行。1、首先找一台带nfc功能的小米手机,模拟加密门禁卡。此时步骤1的小米手机,相当于是未加密的门禁卡)_小米nfc加密卡模拟不了

随便推点

语音处理:Python实现dBFS刻度和采样值相互转换_dbfs 频域-程序员宅基地

文章浏览阅读781次。以对数域常用的dBFS刻度为例,支持主流音频信号位深:整型16/24/32位和浮点32位,编写Python实现对数域和采样值单位互换功能_dbfs 频域

SOD(显著性目标检测)数据集_sod数据集-程序员宅基地

文章浏览阅读4k次,点赞5次,收藏29次。显著性目标检测常用十种数据集:SOD,提取码:f7uqDUT-OMRON,提取码:wqpnMSRA-B,提取码:rfrbSOC,提取码:d5b9SED2,提取码:q4iaHKU-IS,提取码:2f1iPASCAL-S,提取码:naaxDUTS,提取码:a5w7THUR-15K,提取码:ptk9ECSSD..._sod数据集

线程基础:多任务处理(18)——MESI协议以及带来的问题:伪共享-程序员宅基地

文章浏览阅读3.9k次,点赞10次,收藏17次。本文和后续文章将着眼CPU的工作原理阐述伪共享的解决方法和volatile关键字的应用。

Google Earth Engine (GEE) ——代码编辑器_gee怎么新建代码文件-程序员宅基地

文章浏览阅读1.7k次。问题 在线代码编辑器的主要功能是什么? 在学习 GEE 的过程中,我可以去哪里寻求帮助? 如何搜索和导入数据集? 如何创建、共享和保存脚本? 目标 了解代码编辑器中可用的工具 加载图像集合并将其过滤为相关图像 使用几何工具创建研究区域 代码编辑器概述GEE 有一个称为代码编辑器的集成开发环境 (IDE)。代码编辑器有许多功能可以帮助我们在本教程中更轻松地在这种环境中进行编程。有关详尽说明,请参阅GEE 用户指南..._gee怎么新建代码文件

关于 Eclipse 使用 Maven 打包,每次都需要下载 jar 包的问题_maven打包每次都要下载依赖-程序员宅基地

文章浏览阅读6.2k次。在是Eclipse开发的时候,使用Maven打包每次都需要联网下载jar包。第一次需要下载这个可以理解。但是每次都需要下载,就有点问题了...重点是,所用的网络不能访问Maven的私服,所以每次打包都断开网络。这样很麻烦,找资料发现勾选下图中的 Offline(离线) 就可以解决问题了。注意:当你需要下载其他的依赖时,就需要把这个勾去掉,不然连接不上仓库哦。..._maven打包每次都要下载依赖

pg_cancel_backend()和pg_terminate_backend()_pg_cancel_backend pg_ter-程序员宅基地

文章浏览阅读8k次,点赞2次,收藏6次。先看下两个函数的官方解释: pg_cancel_backend() 取消后台操作,回滚未提交事物pg_terminate_backend() 中断session,回滚未提交事物这里和oracle类似kill session的操作是pg_terminate_backend() pg_cancel_backend() 举例:session A:postgres=# create table tb..._pg_cancel_backend pg_ter

推荐文章

热门文章

相关标签