1. 背景

一个需求 : 要将手机上的画面和音频 投屏 到 车机的Android屏幕上。

车机有一个支持OTG的USB-A口,由于设备有限,我们有一个USB-A转HDMI转接口,一跟HDMI线,一个USB-C的拓展坞 (包括HDMI口,两个USB-A口,一个网口),我们将这几根线接在一起,成功将手机和车机连在了一起。

接着,我们在网上找到了一个 jiangdongguo/AndroidUSBCamera ,我们使用Android Studio打开编译安装到车机,并将车机的Usb modeDevice mode切换为Host Mode,这个时候,打开USB摄像头这个设备,就可以看到手机上的画面显示到车机上了。

使用yorkZJC/UvcCameraDemo这个库也可以成功

和这个相关的所有的项目,基本都是基于 saki4510t/UVCCamera 这个开源项目来改的

那么,我们有了以下两个疑问

  • 是如何读取到手机上的画面,显示到车机上的 ?
  • 为什么只有画面,没有声音 ?

2. 是如何读取到手机上的画面,显示到车机上的 ?

带着这个疑问,看了下AndroidUSBCamera的代码。

原来,现在所有主流操作系统都已提供UVC设备驱动,因此符合UVC规格的硬件设备在不需要安装任何的驱动程序下即可在主机中正常使用。使用UVC技术的包括摄像头、数码相机、类比影像转换器、电视棒及静态影像相机等设备。

UVC全称USB Video Class,即 USB视频类,是一种为USB视频捕获设备定义的协议标准。

是Microsoft与另外几家设备厂商联合推出的为USB视频捕获设备定义的协议标准,已成为USB org标准之一。

而项目中,对USB Camera (UVC设备)的使用和视频数据采集进行了高度封装。

//开始进行预览
private void startPreview() {mCameraHelper.startPreview(mUVCCameraView);
}

通过startPreview方法,会调用到handleStartPreview方法

public void handleStartPreview(final Object surface) {if (DEBUG) Log.v(TAG_THREAD, "handleStartPreview:");if ((mUVCCamera == null) || mIsPreviewing) return;try {mUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, mPreviewMode, mBandwidthFactor);// 获取USB Camera预览数据,使用NV21颜色会失真// 无论使用YUV还是MPEG,setFrameCallback的设置效果一致mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP);} catch (final IllegalArgumentException e) {try {// fallback to YUV modemUVCCamera.setPreviewSize(mWidth, mHeight, 1, 31, UVCCamera.DEFAULT_PREVIEW_MODE, mBandwidthFactor);} catch (final IllegalArgumentException e1) {callOnError(e1);return;}}if (surface instanceof SurfaceHolder) {mUVCCamera.setPreviewDisplay((SurfaceHolder) surface);}if (surface instanceof Surface) {mUVCCamera.setPreviewDisplay((Surface) surface);} else {mUVCCamera.setPreviewTexture((SurfaceTexture) surface);}mUVCCamera.startPreview();mUVCCamera.updateCameraParams();synchronized (mSync) {mIsPreviewing = true;}callOnStartPreview();
}

最终调用到nativeSetPreviewDisplay方法

static jint nativeSetPreviewDisplay(JNIEnv *env, jobject thiz,ID_TYPE id_camera, jobject jSurface) {jint result = JNI_ERR;ENTER();UVCCamera *camera = reinterpret_cast<UVCCamera *>(id_camera);if (LIKELY(camera)) {ANativeWindow *preview_window = jSurface ? ANativeWindow_fromSurface(env, jSurface) : NULL;result = camera->setPreviewDisplay(preview_window);}RETURN(result, jint);
}

这时候,我们就可以预览到手机上的画面了。

3. 为什么只有画面,没有声音 ?

这个时候,我们可以发现,车机上只显示出了画面,没有声音播放的。

看了下AndroidUSBCamera里的代码,当点击了录像按钮,调用startPusher方法,回调里type==0,表示是aac audio stream的,但实际测试中,永远都只会收到type==1的情况,而收不到type==0的情况。

mCameraHelper.startPusher(params, new AbstractUVCCameraHandler.OnEncodeResultListener() {@Overridepublic void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) {// type = 1,h264 video streamif (type == 1) {FileUtils.putFileStream(data, offset, length);}// type = 0,aac audio streamif(type == 0) {trackplayer.write(data, offset, length);//往track中写数据}}@Overridepublic void onRecordResult(String videoPath) {if(TextUtils.isEmpty(videoPath)) {return;}new Handler(getMainLooper()).post(() -> Toast.makeText(USBCameraActivity.this, "save videoPath:"+videoPath, Toast.LENGTH_SHORT).show());}
});

在Github 的issue上,我也看到了这个问题 : 可以支持USB的音频输入吗?

看上去大家也有同样的问题

这时候,找到的USB摄像头这个应用市场上的app,却是可以在投屏的同时,播放出声音的。

反编译了这个apk,可以看到它的so里面,有一个libUSBAudio.so,看上去就是用来处理音频的so

我们在网上查找了一下这个so,得知

libusb是一底层的API,可以跨平台实现。

基于libusb可以获取到usb mac的pcm流数据,从而可以读取到音频。

libusb库使用C语言编写,在Android中使用该库需要用到JNI技术。

github : libusb/libusb:用于访问 USB 设备的跨平台库

然后,我们找到了一个libusb的库 jim0608/android_usbaudio: 基于libusb,实现无驱动获取USBAudio

当然,这个库本身是有点问题的,但大体思路可以参考

由于libusb库使用到了JNI,所以我们需要先配置好NDK,其对应版本为21.0.6113669


代码中有一个OnDeviceConnectListener,当把视频线插到车机的USB-A口的时候,

onAttach方法就会被调用,这个时候会调用requestPermission去请求权限。

当连接上的时候,会记录下mCtrlBlockmCtrlBlock

private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() {@Overridepublic void onAttach(UsbDevice device) {Log.i(TAG, "onAttach: " + device);mUSBMonitor.requestPermission(device);}@Overridepublic void onDettach(UsbDevice device) {Log.i(TAG, "onDettach: " + device);}@Overridepublic void onConnect(UsbDevice device, USBMonitor.UsbControlBlock ctrlBlock, boolean createNew) {Log.i(TAG, "onConnect: " + device);for (int interfaceIndex = 0; interfaceIndex < device.getInterfaceCount(); interfaceIndex++) {if (device.getInterface(interfaceIndex).getInterfaceClass() == USB_CLASS_AUDIO){mCtrlBlock = ctrlBlock;break;}}mAudioDevice = device;}//...省略...};

接着,我们就可以去初始化音频了

 mCtrlBlock = mUSBMonitor.getDevice(mDevice);mUsbAudio.initAudio(mCtrlBlock);

然后再开始捕获

mUsbAudio.startCapture();

这个时候,我们在手机端播放音乐,车机的音响就会输出声音了。

播放声音其实是用过AudioTrack来播放的

在Android中,播放声音可以用MediaPlayer和AudioTrack

区别如下

MediaPlayer AudioTrack
支持格式 MP3,AAC,WAV,OGG,MIDI等 已经解码的PCM流,或WAV格式的音频文件(大部分是PCM流)
解码器 在framework层创建对应音频解码器 不创建解码器,所以只能播放无需解码的WAV文件
联系 在framework层还是会创建AudioTrack

这里,有个采样率的问题,得找到合适的采样率,否则会有播放声音不清晰、白噪音等情况。

结合在一起使用

AndroidUSBCameraandroid_usbaudio结合起来,就可以实现既播放视频,同时播放出声音的效果了。

4. AudioTrack基础的使用

最后,介绍下AudioTrack基础的使用

AudioTrack 的构造方法

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) { ... }

streamType 音频流类型

AudioManager.STREAM_MUSIC:用于音乐播放的音频流。
AudioManager.STREAM_SYSTEM:用于系统声音的音频流。
AudioManager.STREAM_RING:用于电话铃声的音频流。
AudioManager.STREAM_VOICE_CALL:用于电话通话的音频流。
AudioManager.STREAM_ALARM:用于警报的音频流。
AudioManager.STREAM_NOTIFICATION:用于通知的音频流。
AudioManager.STREAM_BLUETOOTH_SCO:用于连接到蓝牙电话时的手机音频流。
AudioManager.STREAM_SYSTEM_ENFORCED:在某些国家实施的系统声音的音频流。
AudioManager.STREAM_DTMF:DTMF音调的音频流。
AudioManager.STREAM_TTS:文本到语音转换(TTS)的音频流。

sampleRateInHz 采样率

播放的音频每秒钟会有多少次采样,一般为44100,最好是通过代码动态获取采样率,其他常见的采样率还有

96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000,
7350,

如果发现播放后声音不清晰、白噪音等情况,可以调整这个采样率值

channelConfig 声道数

单声道AudioFormat.CHANNEL_IN_MONO,双声道AudioFormat.CHANNEL_IN_STEREO,建议选择单声道

audioFormat 数据位宽

只支持AudioFormat.ENCODING_PCM_8BIT(8bit)AudioFormat.ENCODING_PCM_16BIT(16bit)两种,后者支持所有Android手机

bufferSizeInBytes 音频缓冲区大小

建议使用AudioTrack.getMinBufferSize()这个方法获取

int bufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_HZ,channelConfig, AudioFormat.ENCODING_PCM_16BIT);

mode 播放模式

有两种播放模式:

  • MODE_STATIC : 一次性将所有数据都写入播放缓冲区中,简单高效,一般用于铃声,系统提醒音,内存比较小的。
  • MODE_STREAM : 需要按照一定的时间间隔,不断的写入音频数据,理论上它可以应用于任何音频播放的场景。

AudioTrack 播放示例

初始化

int bufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_HZ,AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);track = new AudioTrack(AudioManager.STREAM_MUSIC,SAMPLE_RATE_HZ,channelConfig,AudioFormat.ENCODING_PCM_16BIT,bufSize,AudioTrack.MODE_STREAM);
track.play();

写入数据

public void pcmData(byte[] data) {track.write(data, 0, data.length);
}

停止播放,销毁资源

if(audioTrack.getState() != AudioTrack.STATE_UNINITIALIZED){audioTrack.stop();audioTrack.release();
}

5.本文代码下载

本文Demo下载地址 : Android UVC USBCamera投屏Demo

参考 :
一篇文章带你了解Android Usb摄像头
这可能是介绍Android UvcCamera最详细的文章了
Android音频系统AudioTrack使用方法详解
ffmpeg开发之旅(8):Android UVC Camera(USB摄像头)开发核心技术详解
Android音视频录制与播放功能简述
Android UCV 同时打开多路摄像头
Android从USB声卡录制高质量音频-----使用libusb读取USB声卡数据
基于libusb库、uac协议,获取Audio声音数据
Android/linux从usb声卡获取音频(使用libusb库)—监听“纯麦”(五)

Android USBCamera投屏 - 利用UVC协议将手机上的画面有线投屏到Android车机的屏幕上相关推荐

  1. android电视查看百度网盘,如何将手机百度网盘视频投屏到电视上观看?

    原标题:如何将手机百度网盘视频投屏到电视上观看? 点击上方运营进化岛 "设为星标"⭐ 电视是我新家最后置办的一个大家电,因为觉得使用率会特别低,摆在那里落灰,犹豫要不要买.现在追剧 ...

  2. android7 显示到pc,安卓手机上的画面怎么投屏到Win7电脑上?超详细投屏方法看这里!...

    软件大小: 1.89MB 软件版本: 1.4.9.18 软件类型: 手机相关 查看详情 直接下载 虽然说现在的智能手机的屏幕的尺寸越做越大,有的手机的屏幕尺寸甚至都可以跟平板电脑媲美了,但是尺寸再大的 ...

  3. Android 12 预览版发布,64G手机用户:我又活了 #IT资讯# #Android# #手机系统#

    [CSDN 编者按]2021 年 2 月 18 日,全球使用人数最多的移动操作系统--Android,正式发布了其最新版本 Android 12 DP1 (Developer Preview 开发者预 ...

  4. 截屏没有了_华为手机居然有6种截屏花招,很多人不知道,你会用哪几种?

    华为手机越来越受国民的欢迎,而且华为手机的功能挺多挺实用的.就比如:支持画"一"进行分屏操作,可以让我们一边追剧一边聊天,真的特别方便. 我使用华为手机也已经有三个年头了,在日常生 ...

  5. android广播内容显示在屏幕上,在Android本机来电屏幕上弹出窗口,例如真正的来电者Android应用...

    我也在努力(在这里理解你可能是错误的).您想要实现的是在Android 4.2(Jelly Bean)中显示该活动.我只是延迟显示活动.我在其他类中使用过PhoneStateListener.我可以在 ...

  6. android 文件管理 smb,【Android 冷知识】利用SMB协议远程查看电脑文件或者其他存储设备...

    1.说明 为什么说是冷知识,因为大多数项目用不到,所以归类为冷知识. 本文主要介绍如何通过手机来浏览同局域网内电脑上的文件或者同局域网内其他存储设备上的文件(存储设备有SMBA服务). 2.用到的资源 ...

  7. 鸿蒙电视投屏软件,鸿蒙打造 未来电视就这样!荣耀智慧屏PRO深度评测

    四.投屏功能体验:满满的都是创新点 笔者在过去对于传统电视的体验评测当中,对于传统电视投屏功能使用之麻烦感受颇深:要么外接一个电视盒子,要么安装第三方投屏软件,要么寄希望于视频软件的原生投屏功能. 在 ...

  8. Android平台基于asmack实现XMPP协议中的PubSub机制

    Android平台基于asmack实现XMPP协议中的PubSub机制 本文主要介绍,在Android平台上基于asmack包,实现的PubSub机制,在PubSub中最重要的概念是节点Node,几乎 ...

  9. android设备登录是什么手机_手机玩游戏屏幕太小,教你怎么一分钟把手机游戏画面投到大屏幕上...

    把手机投屏到电脑上会带来很多便捷之处,尽管市面上部分手机屏幕的尺寸够大,但还是有很多手机屏幕不能满足我们的日常需求,例如玩一些大型游戏,屏幕小玩得不尽兴,相信很多人都会思考,有没有一些方法,可以将我们 ...

  10. automotive 安卓开发_谷歌首次提及「Android Automotive」,是安卓车机系统亮相前奏?...

    谷歌近日放出了Android 6.0 Marshmallow版本的兼容性测试纲要(ACDD,Android Compatibility Definition Document),如果细细阅读,你会意外 ...

最新文章

  1. OpenCV矩阵运算
  2. LeNet5,AlexNet,MobileNet它们的前身你知道吗?
  3. Linux下mount FreeBSD分区
  4. 【Python】 获取MP3信息replica
  5. Redis主从同步和持久化
  6. Linux LVM学习总结——扩展卷组VG
  7. web通讯录之搜索功能
  8. php裁剪图片并上传源码,改写jcrop插件+php的图片上传实现与裁剪一体化
  9. B. Balanced Lineup
  10. Android 节日短信送祝福(功能篇:2-短信历史记录Fragment的编写)
  11. 移植qt5.3.1到arm
  12. 理解go func背后发生了什么?
  13. Cracking the Coding Interview Q1.2
  14. 模拟实现和深入理解Node Stream内部机制
  15. Python:利用python代码编程实现将视频的avi格式转换为MP4格式
  16. 【Python计量】statsmodels对虚拟变量进行回归
  17. 【图解版】B2C电商平台解决方案
  18. 〖Python 数据库开发实战 - Python与MySQL交互篇⑧〗- 项目实战 - 开发新闻管理系统(项目展示)
  19. 逆水寒服务器维护,逆水寒11月29日更新到几点进游戏 逆水寒更新维护公告
  20. 【工程】Pulp-Amply(三)

热门文章

  1. 蚂蚁区块链第1课 蚂蚁10大区块链解决方案及应用场景
  2. Winrar 5.60 beta 4 个性破解注册码(2018.5.22)
  3. 中信建投软件测试,中信建投笔试经验-范例
  4. 十大免费java开源商城系统
  5. multisimbcd码_8421BCD码转换成5421BCD码
  6. 数学建模清风微信公众号的习题答案(基础篇-操作题)
  7. 安装并使用达梦数据库
  8. jvisualvm使用
  9. windows查看本机ip地址
  10. mxf转换工具(Aiseesoft MXF Converter) v9.2.36