Android 使用ffmpeg软编码 将摄像头采集视频编码成视频文件

2023-11-09

Android 使用ffmpeg软编码 将摄像头采集视频编码成视频文件。
这次代码实现的是视频采集的功能,Android 通过jni 调用ffmpeg 编码yuv数据变成视频文件。
先上代码:

//编码器上下文保存的实体
struct EnCodeBean {
    FILE *f;
    AVFrame *frame;
    AVPacket *pkt;
    AVCodecContext *c = NULL;
    int  width=0;
    int height=0;
};

EnCodeBean *videoEncodeObj = NULL;

//编码每一帧
static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *outfile);

//初始化编码器
static jint initEnCodec(JNIEnv *env, jobject jobject1, jstring path, jint w, jint h) {
    videoEncodeObj = new EnCodeBean;
    char filename[300], *codec_name;
    const AVCodec *codec;
    int i, ret, x, y;
    uint8_t endcode[] = {0, 0, 1, 0xb7};
    char errorArr[500];

    videoEncodeObj->width=w;
    videoEncodeObj->height=h;

//    filename = "/storage/emulated/0/Download/jason_video.h265";
    //获取路径参数
    const char *_path = env->GetStringUTFChars(path, 0);
    sprintf(filename, "%s", _path);
    env->ReleaseStringUTFChars(path, _path);

    AVCodecID videoCodec = AV_CODEC_ID_MPEG2VIDEO; // 编码器类型

    /* 查找编码器 */
    codec = avcodec_find_encoder(videoCodec);
    LOGJASON(FMT_TAG, avcodec_get_name(videoCodec));
    if (!codec) {
        sprintf(errorArr, "Codec '%s' not found\n", avcodec_get_name(videoCodec));
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    //编码上下文
    videoEncodeObj->c = avcodec_alloc_context3(codec);
    if (!videoEncodeObj->c) {
        sprintf(errorArr, "Could not allocate video codec context\n");
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    //编码后存储的包packet
    videoEncodeObj->pkt = av_packet_alloc();
    if (!videoEncodeObj->pkt) {
        sprintf(errorArr, "packet init error\n");
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    /* 设置参数 */
    videoEncodeObj->c->bit_rate = 639 * 1000;//码率 质量
    /* resolution must be a multiple of two */
    videoEncodeObj->c->width = w;
    videoEncodeObj->c->height = h;
    /* frames per second */
    videoEncodeObj->c->time_base = (AVRational) {1, 25};//时间单位 时基
    videoEncodeObj->c->framerate = (AVRational) {25, 1};//频率

    videoEncodeObj->c->gop_size = 10;
    videoEncodeObj->c->max_b_frames = 1;
    videoEncodeObj->c->pix_fmt = AV_PIX_FMT_YUV420P;//图像格式

    if (codec->id == AV_CODEC_ID_H264)
        av_opt_set(videoEncodeObj->c->priv_data, "preset", "slow", 0);

    /* 打开编码器 */
    ret = avcodec_open2(videoEncodeObj->c, codec, NULL);
    if (ret < 0) {
        sprintf(errorArr, "Could not open codec: %s\n", av_err2str(ret));
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    //打开创建文件
    videoEncodeObj->f = fopen(filename, "wb");
    if (!videoEncodeObj->f) {
        sprintf(errorArr, "Could not open %s\n", filename);
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    //每帧储存内存创建
    videoEncodeObj->frame = av_frame_alloc();
    if (!videoEncodeObj->frame) {
        sprintf(errorArr, "Could not allocate video frame\n");
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }
    videoEncodeObj->frame->format = videoEncodeObj->c->pix_fmt;
    videoEncodeObj->frame->width = videoEncodeObj->c->width;
    videoEncodeObj->frame->height = videoEncodeObj->c->height;

    //根据参数构建buf
    ret = av_frame_get_buffer(videoEncodeObj->frame, 32);//32
    if (ret < 0) {
        sprintf(errorArr, "Could not allocate the video frame data\n");
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }
    LOGJASON("init ok %p", videoEncodeObj);
    return 0;
}

//编码每一帧
static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,
                   FILE *outfile) {
    int ret;
    char errorStr[500];

    /* send the frame to the encoder */
    if (frame)
        LOGJASON("Send frame %lld\n", frame->pts);

    //开始编码
    ret = avcodec_send_frame(enc_ctx, frame);
    if (ret < 0) {
        sprintf(errorStr, "Error sending a frame for encoding\n");
        LOGJASON(FMT_TAG, errorStr);
        return;
    }

    while (ret >= 0) {
        //接受编码
        ret = avcodec_receive_packet(enc_ctx, pkt);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            sprintf(errorStr, "Error during encoding\n");
            LOGJASON(FMT_TAG, errorStr);
            return;
        }

        LOGJASON("Write packet %lld (size=%5d)\n", pkt->pts, pkt->size);
        //写入文件
        fwrite(pkt->data, 1, pkt->size, outfile);
        av_packet_unref(pkt);
    }
}

//Java推送流的编码方法
static jint pushFrame(JNIEnv *env, jobject jobject1, jlong pts, jbyteArray jbyteArray1) {

    //获取yuv 流
    int len=env->GetArrayLength(jbyteArray1);
    unsigned char *buf = new unsigned char[len];
    env->GetByteArrayRegion(jbyteArray1, 0, len, reinterpret_cast<jbyte *>(buf));

    /* make sure the frame data is writable */
    int i, ret;
    char errorArr[500];
    i = pts;

    ret = av_frame_make_writable(videoEncodeObj->frame);
    if (ret < 0) {
        sprintf(errorArr, "av_frame_make_writable is error");
        LOGJASON(FMT_TAG, errorArr);
        return -1;
    }

    int frameSize = videoEncodeObj->width*videoEncodeObj->height;

    //将流数据复制进入frame buffer
    memcpy(videoEncodeObj->frame->data[0], buf, frameSize);
    memcpy(videoEncodeObj->frame->data[1], buf+frameSize, frameSize/4);
    memcpy(videoEncodeObj->frame->data[2], buf+frameSize+frameSize/4, frameSize/4);

    videoEncodeObj->frame->pts = i;

    /* 开始编码 */
    encode(videoEncodeObj->c, videoEncodeObj->frame, videoEncodeObj->pkt, videoEncodeObj->f);

    //释放引用
    env->ReleaseByteArrayElements(jbyteArray1, reinterpret_cast<jbyte *>(buf), 0);
    return 0;
}

//关闭编码功能
static jint endClose(JNIEnv *env, jobject jobject1) {
    uint8_t endcode[] = {0, 0, 1, 0xb7};
    /* 清空缓存区 */
    encode(videoEncodeObj->c, NULL, videoEncodeObj->pkt, videoEncodeObj->f);

    /* 写入文件尾部 */
    fwrite(endcode, 1, sizeof(endcode), videoEncodeObj->f);
    fclose(videoEncodeObj->f);

    //释放上下文
    avcodec_free_context(&videoEncodeObj->c);
    av_frame_free(&videoEncodeObj->frame);
    av_packet_free(&videoEncodeObj->pkt);
    LOGJASON("is end ok");

    delete videoEncodeObj;
    videoEncodeObj = NULL;

    LOGJASON("free out is ok");
    return 0;
}

static jint showMsg(JNIEnv *env, jobject jobject1, jstring jstring1) {
    const char *instr = env->GetStringUTFChars(jstring1, 0);
    LOGJASON(FMT_TAG, instr);
    env->ReleaseStringUTFChars(jstring1, instr);
    return 0;
}

//----------------------------------jni 动态注册方法-----------------------------------------

static JNINativeMethod javaMethods[] = {
        {"initEnCodec", "(Ljava/lang/String;II)I", (void *) initEnCodec},
        {"pushFrame",   "(J[B)I",                  (void *) pushFrame},
        {"showMsg",     "(Ljava/lang/String;)I",   (void *) showMsg},
        {"endClose",    "()I",                     (void *) endClose}
};

jint JNI_OnLoad(JavaVM *vm, void *unused) {
    JNIEnv *env = NULL;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGJASON(FMT_TAG, "和获取env异常");
        return -1;
    }

    const char *className = "com/liyihang/jason/VideoEnCodec";
    int methodNum = sizeof(javaMethods) / sizeof(JNINativeMethod);

    jclass jclass1 = env->FindClass(className);
    if (jclass1 == NULL) {
        LOGJASON(FMT_TAG, "find class error");
        return -1;
    }

    int ret = env->RegisterNatives(jclass1, javaMethods, methodNum);
    if (ret < 0) {
        env->DeleteLocalRef(jclass1);
        return -1;
    }
    env->DeleteLocalRef(jclass1);
    return JNI_VERSION_1_4;
}

Java调用 so库文件:

public class VideoEnCodec {

    static {
        System.loadLibrary("native-lib");
    }


    public native int initEnCodec(String path, int w, int h);
    public native int pushFrame(long pts,byte[] arr);
    public native int endClose();
    public native int showMsg(String msg);
}

调用方法:

    //必须在子线程中运行
    private void handle() {
        int w=320;
        int h=240;
        VideoEnCodec videoEnCodec = new VideoEnCodec();
        //初始化 和 关闭必须成对出现
        videoEnCodec.initEnCodec("/storage/emulated/0/Download/jason_video3.h265", w,h);
        for (int i = 0; i < 90; i++) {
            //推送摄像头采集的每一帧 例如:camera 采集的nv21 数据   i 是pts
            byte[] arr = makeBuf(w,h, i);
            videoEnCodec.pushFrame(i,arr);
        }
        //关闭编码器
        videoEnCodec.endClose();
    }


    //构建yuv假数据
    private byte[] makeBuf(int w, int h, int i){
        /* prepare a dummy image */
        int y,x;
        byte[] arr=new byte[w*h+(w*h)/2];
        /* Y */
        int offset=0;
        for (y = 0; y < h; y++) {
            for (x = 0; x < w; x++) {
                arr[offset]= (byte)
                        (x + y + i * 3);
                offset++;
            }
        }

        /* Cb and Cr */
        for (y = 0; y < h / 2; y++) {
            for (x = 0; x < w / 2; x++) {
                arr[offset]= (byte) (128 + y + i * 2);
                arr[offset+(w*h/4)]= (byte) (64 + x + i * 5);
                offset++;
            }
        }
        return arr;
    }

这里为了方便理解没有适用任何封装,采集摄像头的数据可以看看我的博客中关于摄像头采集这块的文章,这里我们方便理解手动造出了每帧的图像数据。
代码中也有有详细主是可以方便理解。

代码补充:

#include <jni.h>
#include <string>
#include <cstdio>
#include <android/log.h>
#include <fcntl.h>
#include <ctime>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <ctime>
#include <dlfcn.h>

extern "C" {
#include "include/libavutil/imgutils.h"
#include "include/libavutil/samplefmt.h"
#include "include/libavformat/avformat.h"
#include "include/libavutil/frame.h"
#include "include/libavutil/mem.h"
#include "include/libswscale/swscale.h"
#include "include/libswresample/swresample.h"
#include "include/libavutil/opt.h"
#include "include/libavfilter/avfilter.h"
#include "include/libavcodec/avcodec.h"
#include "include/libavfilter/buffersink.h"
#include "include/libavfilter/buffersrc.h"
}

// log标签
#define  FMT_TAG    "%s"
// 定义info信息
#define LOGJASON(...) __android_log_print(ANDROID_LOG_INFO,"jason_jni",__VA_ARGS__)

#if defined(__arm64__) || defined(__aarch64__)
#define JSONT 1
#else
#define JSONT 2
#endif

接下来还会更新 声音采集,如果有兴趣可以继续关注我的博客。

关于Android jni ffmpeg 环境搭建可以参考我博客中其他文章, 网上这类文章很多就不在重复了

转载时候一定要注明出处 尊重原创 谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 使用ffmpeg软编码 将摄像头采集视频编码成视频文件 的相关文章

  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 如何使用 IF 检查 TextView 可见性

    我有一个 onCheckedChangeListener 来根据选择的单选按钮显示文本视图 我有 1 个疑问和 1 个难题 想知道是否有人可以帮助我 问题 您能否将单选组默认检查值设置为 否 单选按钮 以便一开始就不会检查任何内容 问题 如
  • 推荐用于视频编码的最佳质量/性能 H264 编码器?

    我正在寻找一种速度快 需要较少 CPU 功率并生成质量非常好的 mp4 视频的视频编码器 输入视频可以是任何格式并由用户上传 我唯一知道的是 FFMPEG 库 还有其他更好的吗 该程序必须有一个我感兴趣的批处理实用程序 exe 如果您愿意分
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • 如何默认在 ActionOpenDocument 意图中显示“内部存储”选项

    我需要用户选择一个自定义文件类型的文件 并将其从 Windows 文件资源管理器拖到 Android 设备上 但默认情况下内部存储选项不可用 当我使用以下命令启动意图时 var libraryIntent new Intent Intent
  • JRE 系统库 [WebSphere v6.1 JRE](未绑定)

    将项目导入 Eclipse 后 我的构建路径中出现以下错误 JRE System Library WebSphere v6 1 JRE unbound 谁知道怎么修它 右键单击项目 特性 gt Java 构建路径 gt 图书馆 gt JRE
  • getResourceAsStream() 可以找到 jar 文件之外的文件吗?

    我正在开发一个应用程序 该应用程序使用一个加载配置文件的库 InputStream in getClass getResourceAsStream resource 然后我的应用程序打包在一个 jar文件 如果resource是在里面 ja
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • 增加活动的屏幕亮度

    显然 Android 操作系统中至少有三种不同的技术可以改变屏幕亮度 其中两个在纸杯蛋糕之后不再起作用 而第三个被接受的技术显然有一个错误 我想在单视图活动开始时增加屏幕亮度 然后在活动结束时将亮度恢复为用户设置 没有按钮 没有第二个视图或
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 静态变量的线程安全

    class ABC implements Runnable private static int a private static int b public void run 我有一个如上所述的 Java 类 我有这个类的多个线程 在里面r
  • 实现滚动选择 ListView 中的项目

    我想使用 ListView 您可以在其中滚动列表来选择一个项目 它应该像一个 Seekbar 但拇指应该是固定的 并且您必须使用该栏来调整它 我面临的一个问题是 我不知道这种小部件是如何调用的 这使得我很难搜索 所以我制作了下面这张图片 以
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 如何修复 JNLP 应用程序中的“缺少代码库、权限和应用程序名称清单属性”?

    随着最近的 Java 更新 许多人都遇到了缺少 Java Web Start 应用程序的问题Codebase Permissions and Application name体现属性 尽管有资源可以帮助您完成此任务 但我找不到任何资源综合的
  • 如何将 google+ 登录集成到我的 Android 应用程序中?

    大家好 实际上我需要通过我的应用程序从 google 登录人们 现在我阅读了 google 上的文档 其中指出 要允许用户登录 请将 Google Sign In 集成到您的应用中 初始化 GoogleApiClient 对象时 请求 PL
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • 如何快速准备大厂秋招面试中的算法

    如何快速准备大厂秋招面试中的算法 数据结构 1 栈 1 1 栈的概述 1 2 栈的常规操作 1 3 用js封装栈 1 4 栈的应用 2 队列 2 1 队列的概述 2 2 队列的常规操作 2 3 用js封装队列 2 4 队列的应用 3 链表
  • FreeRTOS学习笔记6(任务通知)

    1 任务通知函数及其知识点的介绍 下面是任务通知得一些特点 1 我们使用队列 信号量 事件组等等方法时 并不知道对方是谁 使用任务通知时 可以明确指定 通 知哪个任务 2 使用任务通知时 任务结构体TCB中就包含了内部对象 可以直接接收别人
  • MacbookPro安装前端开发环境的爬坑之旅

    文章目录 前言 一 MacbookPro的系统认知 二 强大的触控板 16种姿势带你飞 1 熟悉触控板 2 熟悉怎样下载APP 3 开始前端环境的搭建 总结 前言 2021年1月26日 一个前端开发小菜鸟拿到人生第一台MacbookPro的
  • 4.2 类

    类 类声明 类体 变量 成员变量 实例变量和类变量 局部变量 方法里面声明的变量 4 2 1类声明 类声明 class 类名 public class People 公共类 public class People String name i
  • Java .io_java IO

    java IO 主要内容 java io File类的使用 IO原理及流的分类 文件流 FileInputStream FileOutputStream FileReader FileWriter 缓冲流 BufferedInputStre
  • Linux十大常用命令

    1 gt 查看文件信息 ls ls是英文单词list的简写 其功能为列出目录的内容 是用户最常用的命令之一 它类似于DOS下的dir命令 Linux文件或者目录名称最长可以有265个字符 代表当前目录 代表上一级目录 以 开头的文件为隐藏文
  • 机器视觉之医学诊断应用

    https www toutiao com a6668252530897584644 随着药品和医疗器械安全性问题重要性的不断提升 越来越多的生产厂商将机器视觉技术引入实际生产中来 以达到提高生产效率 加强产品质量保障的目的 同样 在医疗系
  • Windows环境安装redis-dump

    安装msys2 x86 64 20190524 exe http repo msys2 org distrib x86 64 msys2 x86 64 20190524 exe rubyinstaller devkit 2 7 1 1 x6
  • CSS样式修改的一些技巧

    感觉自己对页面太差了 很多小问题不了解 是时候加强一下这方面 把最近一个小需求 总结一下 自己也欠了好多学习博客补一下 另外这个博客是来自于自己平时学习的总结和看法 基本是原创或者自己看到一些结合了自己的理解 已经有了一些文章 借用了 我的
  • 重启c语言—两个有序链表序列的交集

    7 1 两个有序链表序列的交集 20分 已知两个非降序链表序列S1与S2 设计函数构造出S1与S2的交集新链表S3 输入格式 输入分两行 分别在每行给出由若干个正整数构成的非降序序列 用 1表示序列的结尾 1不属于这个序列 数字用空格间隔
  • 华为OD机试 - 选修课(Java & JS & Python)

    题目描述 给定一个元素类型为小写字符串的数组 请计算两个没有相同字符的元素长度乘积的最大值 如果没有符合条件的两个元素 返回0 输入描述 第一行为第一门选修课学生的成绩 第二行为第二门选修课学生的成绩 每行数据中学生之间以英文分号分隔 每个
  • 汇编笔记

    更新于20190929 1 Intel和AT T汇编 参数是反的 AT T寄存器前加 常量前加 Intel mov rax rcx rcx gt rax mov cl 2 对应AT T movq rcx rax rcx gt rax mov
  • RHEL/centos8.0离线安装n卡驱动,cuda10.1,cudnn7.5,anaconda3,pycharm以及mmdeection和simpledet的搭建

    我最近在两台RHEL8 0的服务器装了这些玩意 特此记录一下 1 离线安装nvidia driver cuda10 1 cudnn7 5 关键因素 显卡型号 Quadro P4000 系统 RHEL 8 0 用 cat etc redhat
  • IPv4与ipv6联系

    IPv4又称互联网通信协议第四版 是网际协议开发过程中的第四个修订版本 也是此协议第一个被广泛部署的版本 但是2019年11月26日 全球所有43亿个IPv4地址已分配完毕 IPV6是互联网工程任务组设计的用于替代IPv4的下一代IP协议
  • Java高级程序设计_JAVA高级程序设计

    恢复内容开始 import java awt import java awt event ActionEvent import java awt event ActionListener import java awt event Mous
  • 12个C语言必背实例

    C语言实例第01期 十进制数转换二进制数 实例代码 include stdio h int main int m n k 定义变量 int a 16 0 printf 请输入一个0 32767之间的数字 n scanf d n printf
  • ImageNet零样本准确率首次超过80%,地表最强开源CLIP模型更新

    关注公众号 发现CV技术之美 本文转自新智元 编辑LRS 开源模型OpenCLIP达成ImageNet里程碑成就 虽然ImageNet早已完成历史使命 但其在计算机视觉领域仍然是一个关键的数据集 2016年 在ImageNet上训练后的分类
  • STM32--STM32CubeMX的Timer3定时1ms功能HAL库操作

    一 STM32CubeMX的设置 时钟源的选择 Crystal Ceramic Resonator 调试方法选择 Serial Wire 时钟输入为40MHz Timer3的参数设置 使能Timer3的中断 点击 Generate Code
  • unigui中的unidbgrid单元格内容太长自动回行

    1 servermodule中customcss中加入
  • Android 使用ffmpeg软编码 将摄像头采集视频编码成视频文件

    Android 使用ffmpeg软编码 将摄像头采集视频编码成视频文件 这次代码实现的是视频采集的功能 Android 通过jni 调用ffmpeg 编码yuv数据变成视频文件 先上代码 编码器上下文保存的实体 struct EnCodeB