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

目录

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]

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第1张图片

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

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第2张图片

 【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第3张图片

图3: 选择[ffmpeg] 

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第4张图片

然后修改Makefile:

文件头部新增如下两行:

CONFIG_PACKAGE_libx264:=1
CONFIG_FFMPEG_CUSTOM_NONFREE:=1

改完是这个样子:

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第5张图片

然后是修改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) \
        

改完是这个样子

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第6张图片

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

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

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

【首发】随身wifi编译/使用ffmpeg方法,包含openwrt和debian_第7张图片

第四步,运行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这些不同分辨率,不同码率下,视频的清晰度到底如何,给不同应用场景下摄像头的选择提供实际的数据。

你可能感兴趣的:(网络,android,实时音视频)