联谊会总结网站建设对外宣传/软文广告怎么写
目录
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();}@Overridepublic void surfaceCreated(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {mSurface = holder.getSurface();initMediaCodec();}@Overridepublic 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这些不同分辨率,不同码率下,视频的清晰度到底如何,给不同应用场景下摄像头的选择提供实际的数据。