【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian

news2024/9/21 3:02:37

目录

1.硬件改造

2.软件改造

3.下一步计划


背景是23年4月入了随身wifi的坑后,发现除了硬件上的改造,软件的可玩性也很大,网上可以找到不少打印机,直播推流,甚至家庭智能硬件的改造教程。笔者是因为改造遥控小车,接触到了随身wifi。因为早年市场上的商用的智能车大多运行linux系统,上面叠加一个摄像头,以mjpg的方式提供视频流。而现在无论是车还是手机,硬件能力都有大幅度提升,h264/h265已经成为主流。随身wifi的硬件是高通410,还没有硬件编码能力,软件编码找了一圈也没有人研究,在此背景下,产生了动手的想法。

1.硬件改造

要让随身wifi接摄像头,必要的硬件改造还是需要的,酷安上可以买到随身wifi的扩展板,也不贵。我追求硬件的体积小,所以就自己焊接了,随身wifi的usb的四个触点接usb摄像头的四个触点,额外引出两根电源线接5v电源。实际就是2公1母的usb延长线,马云家店里应该能买到。注意,焊接前先把随身wifi的系统烧录好,wifi可用,不然焊接了没usb口,连不上电脑,后面的软件改造就执行不下去了。

2.软件改造

第一步,把usb设置成host模式,不然识别不了usb摄像头,成功后/dev/video0设备就出现了,笔者的openwrt是/dev/video0,debian是/dev/video2。另外以下脚本是根据usb口是否接电源判定是否host模式,推荐使用,万一把无线搞坏了,usb口还能救砖。不计较时间成本,有备份恢复大法的可以忽略。

grep 0 /sys/kernel/debug/usb/ci_hdrc.0/device | grep speed
if [ $? -eq 0 ]
then
echo host > /sys/kernel/debug/usb/ci_hdrc.0/role
fi

第二步,debian安装ffmpeg

这个就不多说了,apt安装即可,很方便。但是,cpu占用较高,唯一的好处是插件很全,安装使用方便。

第三步,openwrt编译ffmpeg

为什么又折腾openwrt,笔者是因为debian在网络管理上功能缺失,对openwrt有依赖,所以不得不切换到openwrt。注意,以下教程在openwrt21.2上测试通过,更老的版本没试,主要是ffmpeg对cpu性能要求太高,太老的硬件没有意义。

openwrt的opkg install ffmpeg命令可以安装一个full版本的ffmpeg,但实际使用过程发现没有debian上支持的插件多,基本的x264,preset命令都不支持,所以产生了自己编译的想法,在我写这篇文章的时候,随身wifi的openwrt固件版本都是21.02,苏苏亮亮,水遍编译的固件都是基于github上HandsomeMod魔改而来,所以下载HandsomeMod编译即可

https://github.com/HandsomeMod

首先在menuconfig中,在multimeia下选择ffmpeg,,然后在library下选择libffmpeg-full和libx264.请按以下步骤操作:

图1:选择[Compile with support for patented functionality]

图2: 选择 [libffmpeg-full]和[]

 

图3: 选择[ffmpeg] 

然后修改Makefile:

文件头部新增如下两行:

CONFIG_PACKAGE_libx264:=1
CONFIG_FFMPEG_CUSTOM_NONFREE:=1

改完是这个样子:

然后是修改full中编译的插件:

vim package/feeds/packages/ffmpeg/Makefile

找到 ifeq ($(BUILD_VARIANT),full)


注释掉disable
        ##      $(if $(CONFIG_BUILD_PATENTED),, \
        ##              $(call FFMPEG_DISABLE,decoder,$(FFMPEG_PATENTED_DECODERS)) \
        ##              $(call FFMPEG_DISABLE,encoder,$(FFMPEG_PATENTED_ENCODERS)) \
        ##              $(call FFMPEG_DISABLE,muxer,$(FFMPEG_PATENTED_MUXERS)) \
        ##              $(call FFMPEG_DISABLE,demuxer,$(FFMPEG_PATENTED_DEMUXERS)) \
        ##              $(call FFMPEG_DISABLE,parser,$(FFMPEG_PATENTED_PARSERS))) \


新增enable
        $(call FFMPEG_ENABLE,encoder,$(FFMPEG_CUSTOM_ENCODERS),CONFIG_FFMPEG_CUSTOM_ENCODER) \
        $(call FFMPEG_ENABLE,decoder,$(FFMPEG_CUSTOM_DECODERS),CONFIG_FFMPEG_CUSTOM_DECODER) \
        $(call FFMPEG_ENABLE,muxer,$(FFMPEG_CUSTOM_MUXERS),CONFIG_FFMPEG_CUSTOM_MUXER) \
        $(call FFMPEG_ENABLE,demuxer,$(FFMPEG_CUSTOM_DEMUXERS),CONFIG_FFMPEG_CUSTOM_DEMUXER) \
        $(call FFMPEG_ENABLE,parser,$(FFMPEG_CUSTOM_PARSERS),CONFIG_FFMPEG_CUSTOM_PARSER) \
        $(call FFMPEG_ENABLE,protocol,$(FFMPEG_CUSTOM_PROTOCOLS),CONFIG_FFMPEG_CUSTOM_PROTOCOL) \
        

改完是这个样子

 然后就是编译了,很快,10分钟完事。

编译成功,后可以刷整个system镜像,也可以把编译出来的ipk拷贝到openwrt中手动安装,因为ffmpeg是纯软件实现,对内核和其他组件依赖较小,所以我采用的是后者。

新编译出来如下ipk文件,拷贝到openwrt中安装即可

第四步,运行ffmpeg

openwrt上编译后的ffmpeg与debian上安装的ffmpeg,插件一致,命令就可以保持相同,笔者的目的是把usb摄像头中的视频流取出来,h264编码后,发送到手机,ffmpeg命令可以这么写:

ffmpeg -y -f v4l2 -i /dev/video0 -vcodec libx264 -pix_fmt yuv420p -maxrate 1M -bufsize 4M -f h264 -

最后一个"-"表示输出到pipe中,可以再用其他命令想办法把视频流发送到手机侧

第五步,安卓侧播放流媒体

因为服务端用的ffmpeg,所以很多同学就会想当然认为手机侧播放也依赖ffmpeg,而且网上搜安卓+ffmpeg,出来的大多是怎么编译ffmpeg的so,怎么写jni,怎么在android写java代码调用c代码库,甚至还有github上现成编译好或者叫二次封装过的安卓版ffmpeg库可以用。笔者decompile了不少支持h264流媒体播放的apk也都是这么干的。

上面的这些手段,笔者都尝试过,坑也踩过。现在,都不需要了,因为近几年绝大部分的安卓手机都支持硬件解码了,在安卓侧,直接可以调用安卓自带的MediaCodec接口使用硬解h264流媒体了,代码量还很少,运行效率高。

主要的类:H264SurfaceView,github>>>>>

package com.xxx.h264player;

import android.content.Context;
import android.graphics.Bitmap;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import java.nio.ByteBuffer;

import android.media.MediaCodec;
import android.media.MediaFormat;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;


public class H264SurfaceView extends SurfaceView implements SurfaceHolder.Callback  {
    static int mVideoHeight = 480;
    static int mVideoWidth = 640;
    public float ZOOM[] = {
        100F, 125F, 150F, 175F, 200F
    };
    public static int[] Video_WandH = new int[] { 640, 480 };
    Bitmap bitmap;
    float bx;
    float bx1;
    float by;
    float by1;
    long donw_time;
    public boolean isFirstChange;
    public int streamsize;
    private int targetZoom;
    int mCodecState = -1;
    Surface mSurface;
    MediaCodec mCodec;
    SurfaceHolder mSurfaceHolder;
    private int mFrameIndex = 0;

    public H264SurfaceView(Context context)
    {
        super(context);
        streamsize = 0;
        targetZoom = 0;
        bx1 = 0.0F;
        by1 = 0.0F;
        isFirstChange = true;
        initial();
    }

    public void initMediaCodec() {
        if (mCodecState > 0) return;

        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", Video_WandH[0], Video_WandH[1]);

        /*mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1);
        mediaFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 1);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);*/

        try {
            mCodec = MediaCodec.createDecoderByType("video/avc");
            if (mSurface != null && mSurface.isValid()) {
                mCodec.configure(mediaFormat, mSurface, null, 0);
                mCodec.start();
                mCodecState = 1;
            }
        } catch (Exception e) {
            Log.e("CameraView", " MediaCodec == " + e.getMessage());
            return;
        }
    }

    public H264SurfaceView(Context context, AttributeSet attributeset)
    {
        super(context, attributeset);
        streamsize = 0;
        targetZoom = 0;
        bx1 = 0.0F;
        by1 = 0.0F;
        isFirstChange = true;
        initial();
    }

    public final int getTargetZoom()
    {
        return targetZoom;
    }

    public float getTargetZoomValue()
    {
        return ZOOM[targetZoom];
    }

    public void initial()
    {
        mSurfaceHolder = getHolder();
        mSurfaceHolder.addCallback(this);
        initMediaCodec();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }
 


    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mSurface = holder.getSurface();
        initMediaCodec();
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mSurface != null) {
            mSurface.release();
        }
        mSurface = null;
    }

    public final void setTargetZoom(int i)
    {
        targetZoom = i;
    }

    public void takePicture()
    {
        //AppCameraSurfaceFunction.getAppCameraSurfaceFunctionInstance().CameraTakePicture();
    }

    public int zoomIn()
        throws InterruptedException
    {
        if(targetZoom >= 0 && targetZoom < 4)
        {
            //AppDecodeH264.GlZoomIn();
            targetZoom = targetZoom + 1;
        }
        return targetZoom;
    }

    public void zoomInit()
    {
        //AppDecodeH264.GlZoomInit();
        targetZoom = 0;
    }

    public int zoomOut()throws InterruptedException{
        if(targetZoom > 0 && targetZoom <= 4)
        {
            //AppDecodeH264.GlZoomOut();
            targetZoom = targetZoom - 1;
        }
        return targetZoom;
    }

    private byte[] Bitmap2Bytes() {
        Bitmap bitmap;
        byte abyte0[];
        int i=0;
        bitmap = Bitmap.createBitmap(Video_WandH[0], Video_WandH[1], android.graphics.Bitmap.Config.ARGB_8888);
        abyte0 = new byte[bitmap.getWidth() * bitmap.getHeight() * 4];
        int height = bitmap.getHeight();
        int width = bitmap.getWidth();
        while (i < height) {
            int j = 0;
            while (j < width) {
                abyte0[i * j] = (byte)0;
                j++;
            }
            i++;
        }
        return abyte0;
    }
    
    public void decodeOneFrame(byte[] data, int length) {
        if (mSurface != null && mSurface.isValid()) {
            if (mCodec != null) {
                try {

                    int inputBufferIndex = mCodec.dequeueInputBuffer(0);
                    if (inputBufferIndex >= 0) {
                        ByteBuffer inputBuffer = mCodec.getInputBuffers()[inputBufferIndex];
                        long timestamp = mFrameIndex++ * 1000000 / 30;
                        inputBuffer.clear();
                        inputBuffer.put(data, 0, length);
                        mCodec.queueInputBuffer(inputBufferIndex, 0, length, timestamp, 0);
                    }
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0);
                    while (outputBufferIndex >= 0) {
                        mCodec.releaseOutputBuffer(outputBufferIndex, true);
                        outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 0);
                    }
                } catch (Throwable t) {
                    //Log.e(TAG, "offerDecoder233 == " + t.toString() + t.getMessage());

                    release();
                }

            } 
        }
    }
    public void release() {
        if (mCodec != null) {
            mCodec.release();
            mCodec = null;
        }
    }

    public void stop() {
        if (mCodec != null) {
            mCodec.stop();
            mCodecState = 0;
        }
    }
    
    public void start() {
        if (mCodec != null) {
            mCodec.start();
            mCodecState = 1;
        }
    }
}

3.下一步计划

做了这么多,很多人关心视频质量如何,实际笔者测试过,网络条件正常的时候,分辨率720p以下,视频流畅度上h264和mjpg一样的,延迟h264高些,网络不好的时候,h264流畅度上略好。下一步,笔者计划把抽屉里的几个摄像头都翻出来,测试下1080p,720p,480p这些不同分辨率,不同码率下,视频的清晰度到底如何,给不同应用场景下摄像头的选择提供实际的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/675620.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【前端】Element-UI和Element-Plus的区别

文章目录 对移动端支持区别框架区别开发中使用的区别el-tableel-dialogel-buttonel-date-pickerel-iconechartsIcon图标库变化了组件的插槽slot使用变化了新增组件 来源 对移动端支持区别 Element-UI对应Element2&#xff1a;基本不支持手机版 Element-Plus对应Element3&…

chatgpt赋能python:Python生成A到Z的SEO

Python 生成A到Z的SEO Python 是一种简单易学、功能强大的编程语言&#xff0c;它不仅可用于数据分析、机器学习等领域&#xff0c;还可以用于 SEO 工作中的自动化。在 SEO 中&#xff0c;我们常需要生成不同字母序列来作为页面的标题标签&#xff08;Title Tag&#xff09;&a…

Unencode

打开得到一串密文&#xff0c;看题目形式应该也是一个编码格式 果然是&#xff0c;直接利用网上工具解码就得到flag Uuencode编码 稍微了解了一下Uuencode编码的方式&#xff1a; Uuencode是二进制信息和文字信息之间的转换编码&#xff0c;也就是机器和人眼识读的转换。Uuenco…

从MVC到MVVC:软件架构的演变和迭代

文章目录 1.引言介绍MVC和MVVC架构1. MVC架构2. MVVC架构 2.MVC架构什么是MVC架构MVC的组成部分及其作用1. 模型(Model)2. 视图(View)3. 控制器(Controller) MVC的优点和缺点 3.MVVC架构什么是MVVC架构1. 模型(Model)2. 视图(View)3. 视图模型(View Model) MVVC的组成部分及其作…

2022(一等奖)B22基于时空大数据的多维分析与传统二维分析相结合的大气污染变化研究方法探索

作品介绍 1 作品背景及研究对象 1.1 背景及必要性介绍 2022年春节期间&#xff0c;山东及多地的部分省市明确了禁放烟花爆竹的规定&#xff0c;直接表明了当今大气污染变化的严峻形势&#xff0c;燃放烟花爆竹会严重影响空气环境质量&#xff0c;加剧雾霾天气&#xff0c;产生…

【Linux】CentOS7 设定虚拟机时间为本机当前地区时间的简单操作

目录 情景系统环境操作 情景 新安装的虚拟机时间和当前本地系统时间不一致&#xff0c;现在想要将虚拟机和本机地区的时间调节为一致。 系统环境 CentOS Linux 7 系统界面大致如此。 操作 点击虚拟机界面左上角的 Applications 选项&#xff0c; 选择System-Tools-- Sett…

熵、KL散度和交叉熵

首先我们需要知道&#xff0c;所有的模型都可以看作是一个概率分布模型&#xff0c;包括人脑进行图像分类时也可以看作是一种完美的模型 1、信息量 如果学过通信应该知道香农定义了信息量的的概念&#xff0c;我们能够理解一件事情信息量很大或者很小&#xff0c;但是如何用数…

操作系统 - 进程和线程

✅作者简介&#xff1a;人工智能专业本科在读&#xff0c;喜欢计算机与编程&#xff0c;写博客记录自己的学习历程。 &#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&…

陈香菲九价疫苗接种sop

前文 什么时间 正常时间 ● 正常接种按照接种时间&#xff0c;需要在2023-06-28 08&#xff1a;00-17&#xff1a;00 前往指定卫生院预防接种门诊进行接种。 时间异议 ● 正常来说如果接种时间内来大姨妈或者最近有服用一些药物的话是不能进行接种的&#xff0c;具体药物品…

chatgpt赋能python:Python爬虫防屏蔽策略及技巧

Python爬虫防屏蔽策略及技巧 介绍 爬虫作为一种数据采集工具&#xff0c;越来越广泛地应用于众多领域&#xff0c;包括搜索引擎优化&#xff08;SEO&#xff09;、产品分析、市场调研等等。然而&#xff0c;随着爬虫数量的不断增加&#xff0c;许多网站已经采取了多种方法来屏…

【Spring Cloud Stream 消息驱动】 —— 每天一点小知识

&#x1f4a7; S p r i n g C l o u d S t r e a m 消息驱动 \color{#FF1493}{Spring Cloud Stream 消息驱动} SpringCloudStream消息驱动&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390…

ECharts数据可视化

目录 第一章 什么是ECharts 第二章 搭建环境 2.1 Echarts的下载 2.2 Visual Studio Code下载 第三章 一个简单的可视化展示 第四章 Echarts组件 4.1 标题 4.2 提示框 4.3 工具栏 4.4 图例 4.5 时间轴 4.6 数据区域缩放 4.6.1 滑动条型数据区域缩放 4.6.2 内置型…

chatgpt赋能python:烧录单片机程序:Python的力量

烧录单片机程序&#xff1a;Python的力量 随着技术的发展和人类渴求的不断追求&#xff0c;电子设备的普及程度越来越高。在一个电子设备内部&#xff0c;单片机的应用非常广泛。然而&#xff0c;单片机作为计算机的重要组成部分&#xff0c;也需要相对应的程序来实现不同的功…

实战:Gradle构建工具实践-2023.6.22(测试成功)

实战&#xff1a;Gradle构建工具实践-2023.6.22(测试成功) 目录 推荐文章 https://www.yuque.com/xyy-onlyone/aevhhf?# 《玩转Typora》 实验环境 gitlab/gitlab-ce:15.0.3-ce.0 jenkins/jenkins:2.346.3-2-lts-jdk11 gradle-7.6.1 openjdk 11.0.18实验软件 链接&#xff1…

x-s参数逆向

x-s参数逆向[2023.6.22] 1.提要 众所周知&#xff0c;此次的加密逻辑进入一个叫window._webmsxyw()的函数里面 该函数是封装在一个自执行函数内部&#xff0c;并添加到了window属性里&#xff0c;下面是两种获取思路。 2.扣环境 扣环境的话&#xff0c;只需要在jsdom的docu…

内存耗尽后Redis会发生什么?

作为一台服务器来说&#xff0c;内存并不是无限的&#xff0c;所以总会存在内存耗尽的情况&#xff0c;那么当 Redis 服务器的内存耗尽后&#xff0c;如果继续执行请求命令&#xff0c;Redis 会如何处理呢&#xff1f; 内存回收 使用Redis 服务时&#xff0c;很多情况下某些键…

2023 node 接入腾讯云短信服务,实现发送短信功能

1、在 腾讯云开通短信服务&#xff0c;并申请签名和正文模板 腾讯云短信 https://console.cloud.tencent.com/smsv2 a、签名即是短信的开头。例如 【腾讯云短信】xxxxxxx&#xff1b; b、正文模板即短信内容&#xff0c; 变量部分使用{1}&#xff0c; 数字从1开始累推。例如&a…

Golang每日一练(leetDay0104) 最小高度树、戳气球

目录 310. 最小高度树 Minimum Height Trees &#x1f31f;&#x1f31f; 312. 戳气球 Burst Balloons &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…

MindSpore-TOOD模型权重迁移推理对齐实录

准备工作 环境&#xff1a; wsl2 Ubuntu 20.04 mindspore 2.0.0 python 3.8 pytorch 2.0.1 cpu 基于自己编写的mindspore TOOD项目和MMDetection实现的pytorch权重来做迁移&#xff0c; TOOD论文pytorch mmdetection实现 tood_r50_fpn_1x_coco权重 论文中的代码也是用mmdet…

浅谈前后端交互的基本原理

本文受众人群&#xff1a; 前端/后端开发工程师&#xff1b;Web应用程序设计师&#xff1b;项目经理&#xff1b;产品经理等。 为什么要去了解&#xff1f; 了解前后端交互的基本原理对于从事与Web开发相关的角色的人群是非常重要的。这包括前端开发工程师、后端开发工程师、全…