1. 背景
一個需求 : 要將手機(jī)上的畫面和音頻 投屏 到 車機(jī)的Android屏幕上。
車機(jī)有一個支持OTG的USB-A口,由于設(shè)備有限,我們有一個USB-A轉(zhuǎn)HDMI轉(zhuǎn)接口,一跟HDMI線,一個USB-C的拓展塢 (包括HDMI口,兩個USB-A口,一個網(wǎng)口),我們將這幾根線接在一起,成功將手機(jī)和車機(jī)連在了一起。
接著,我們在網(wǎng)上找到了一個 jiangdongguo/AndroidUSBCamera ,我們使用Android Studio打開編譯安裝到車機(jī),并將車機(jī)的Usb mode
從Device mode
切換為Host Mode
,這個時候,AndroidUSBCamera
會彈出打開USB攝像頭的彈框,我們點擊同意,就可以看到手機(jī)上的畫面顯示到車機(jī)上了。
使用yorkZJC/UvcCameraDemo這個庫也可以成功
和這個相關(guān)的所有的項目,基本都是基于 saki4510t/UVCCamera 這個開源項目來改的
那么,我們有了以下兩個疑問
- 是如何讀取到手機(jī)上的畫面,顯示到車機(jī)上的 ?
- 為什么只有畫面,沒有聲音 ?
2. 是如何讀取到手機(jī)上的畫面,顯示到車機(jī)上的 ?
帶著這個疑問,看了下AndroidUSBCamera
的代碼。
原來,現(xiàn)在所有主流操作系統(tǒng)都已提供UVC設(shè)備驅(qū)動,因此符合UVC規(guī)格的硬件設(shè)備在不需要安裝任何的驅(qū)動程序下即可在主機(jī)中正常使用。使用UVC技術(shù)的包括攝像頭、數(shù)碼相機(jī)、類比影像轉(zhuǎn)換器、電視棒及靜態(tài)影像相機(jī)等設(shè)備。
UVC全稱USB Video Class
,即 USB視頻類,是一種為USB視頻捕獲設(shè)備定義的協(xié)議標(biāo)準(zhǔn)。
是Microsoft與另外幾家設(shè)備廠商聯(lián)合推出的為USB視頻捕獲設(shè)備定義的協(xié)議標(biāo)準(zhǔn),已成為USB org標(biāo)準(zhǔn)之一。
而項目中,對USB Camera (UVC設(shè)備)的使用和視頻數(shù)據(jù)采集進(jìn)行了高度封裝。
//開始進(jìn)行預(yù)覽
private void startPreview() {
mCameraHelper.startPreview(mUVCCameraView);
}
通過startPreview
方法,會調(diào)用到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預(yù)覽數(shù)據(jù),使用NV21顏色會失真
// 無論使用YUV還是MPEG,setFrameCallback的設(shè)置效果一致
mUVCCamera.setFrameCallback(mIFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP);
} catch (final IllegalArgumentException e) {
try {
// fallback to YUV mode
mUVCCamera.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();
}
最終調(diào)用到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);
}
這時候,我們就可以預(yù)覽到手機(jī)上的畫面了。
3. 為什么只有畫面,沒有聲音 ?
這個時候,我們可以發(fā)現(xiàn),車機(jī)上只顯示出了畫面,沒有聲音播放的。
看了下AndroidUSBCamera
里的代碼,當(dāng)點擊了錄像按鈕,調(diào)用startPusher
方法,回調(diào)里type==0,表示是aac audio stream
的,但實際測試中,永遠(yuǎn)都只會收到type==1
的情況,而收不到type==0
的情況。
mCameraHelper.startPusher(params, new AbstractUVCCameraHandler.OnEncodeResultListener() {
@Override
public void onEncodeResult(byte[] data, int offset, int length, long timestamp, int type) {
// type = 1,h264 video stream
if (type == 1) {
FileUtils.putFileStream(data, offset, length);
}
// type = 0,aac audio stream
if(type == 0) {
trackplayer.write(data, offset, length);//往track中寫數(shù)據(jù)
}
}
@Override
public 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攝像頭這個應(yīng)用市場上的app,卻是可以在投屏的同時,播放出聲音的。
反編譯了這個apk,可以看到它的so里面,有一個libUSBAudio.so
,看上去就是用來處理音頻的so
我們在網(wǎng)上查找了一下這個so,得知
libusb是一底層的API,可以跨平臺實現(xiàn)。
基于libusb可以獲取到usb mac的pcm流數(shù)據(jù),從而可以讀取到音頻。
libusb庫使用C語言編寫,在Android中使用該庫需要用到JNI技術(shù)。
github : libusb/libusb:用于訪問 USB 設(shè)備的跨平臺庫
然后,我們找到了一個libusb
的庫 jim0608/android_usbaudio: 基于libusb,實現(xiàn)無驅(qū)動獲取USBAudio
當(dāng)然,這個庫本身是有點問題的,但大體思路可以參考
由于libusb
庫使用到了JNI,所以我們需要先配置好NDK,其對應(yīng)版本為21.0.6113669
代碼中有一個OnDeviceConnectListener
,當(dāng)把視頻線插到車機(jī)的USB-A口的時候,
onAttach方法就會被調(diào)用,這個時候會調(diào)用requestPermission
去請求權(quán)限。
當(dāng)連接上的時候,會記錄下mCtrlBlock
和mCtrlBlock
private final USBMonitor.OnDeviceConnectListener mOnDeviceConnectListener = new USBMonitor.OnDeviceConnectListener() {
@Override
public void onAttach(UsbDevice device) {
Log.i(TAG, "onAttach: " + device);
mUSBMonitor.requestPermission(device);
}
@Override
public void onDettach(UsbDevice device) {
Log.i(TAG, "onDettach: " + device);
}
@Override
public 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();
這個時候,我們在手機(jī)端播放音樂,車機(jī)的音響就會輸出聲音了。
播放聲音其實是用過AudioTrack
來播放的
在Android中,播放聲音可以用MediaPlayer和AudioTrack
區(qū)別如下
MediaPlayer | AudioTrack | |
---|---|---|
支持格式 | MP3,AAC,WAV,OGG,MIDI等 | 已經(jīng)解碼的PCM流,或WAV格式的音頻文件(大部分是PCM流) |
解碼器 | 在framework層創(chuàng)建對應(yīng)音頻解碼器 | 不創(chuàng)建解碼器,所以只能播放無需解碼的WAV文件 |
聯(lián)系 | 在framework層還是會創(chuàng)建AudioTrack |
這里,有個采樣率的問題,得找到合適的采樣率,否則會有播放聲音不清晰、白噪音等情況。
結(jié)合在一起使用
將AndroidUSBCamera
和android_usbaudio
結(jié)合起來,就可以實現(xiàn)既播放視頻,同時播放出聲音的效果了。
4. AudioTrack基礎(chǔ)的使用
最后,介紹下AudioTrack基礎(chǔ)的使用
AudioTrack 的構(gòu)造方法
public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) { ... }
streamType 音頻流類型
AudioManager.STREAM_MUSIC:用于音樂播放的音頻流。
AudioManager.STREAM_SYSTEM:用于系統(tǒng)聲音的音頻流。
AudioManager.STREAM_RING:用于電話鈴聲的音頻流。
AudioManager.STREAM_VOICE_CALL:用于電話通話的音頻流。
AudioManager.STREAM_ALARM:用于警報的音頻流。
AudioManager.STREAM_NOTIFICATION:用于通知的音頻流。
AudioManager.STREAM_BLUETOOTH_SCO:用于連接到藍(lán)牙電話時的手機(jī)音頻流。
AudioManager.STREAM_SYSTEM_ENFORCED:在某些國家實施的系統(tǒng)聲音的音頻流。
AudioManager.STREAM_DTMF:DTMF音調(diào)的音頻流。
AudioManager.STREAM_TTS:文本到語音轉(zhuǎn)換(TTS)的音頻流。
sampleRateInHz 采樣率
播放的音頻每秒鐘會有多少次采樣,一般為44100,最好是通過代碼動態(tài)獲取采樣率,其他常見的采樣率還有
96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000,
7350,
如果發(fā)現(xiàn)播放后聲音不清晰、白噪音等情況,可以調(diào)整這個采樣率值
channelConfig 聲道數(shù)
單聲道AudioFormat.CHANNEL_IN_MONO
,雙聲道AudioFormat.CHANNEL_IN_STEREO
,建議選擇單聲道
audioFormat 數(shù)據(jù)位寬
只支持AudioFormat.ENCODING_PCM_8BIT(8bit)
和AudioFormat.ENCODING_PCM_16BIT(16bit)
兩種,后者支持所有Android手機(jī)
bufferSizeInBytes 音頻緩沖區(qū)大小
建議使用AudioTrack.getMinBufferSize()
這個方法獲取
int bufSize = AudioTrack.getMinBufferSize(SAMPLE_RATE_HZ,channelConfig, AudioFormat.ENCODING_PCM_16BIT);
mode 播放模式
有兩種播放模式:
- MODE_STATIC : 一次性將所有數(shù)據(jù)都寫入播放緩沖區(qū)中,簡單高效,一般用于鈴聲,系統(tǒng)提醒音,內(nèi)存比較小的。
- MODE_STREAM : 需要按照一定的時間間隔,不斷的寫入音頻數(shù)據(jù),理論上它可以應(yīng)用于任何音頻播放的場景。
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();
寫入數(shù)據(jù)
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文章來源:http://www.zghlxwxcb.cn/news/detail-482329.html
參考 :
一篇文章帶你了解Android Usb攝像頭
這可能是介紹Android UvcCamera最詳細(xì)的文章了
Android音頻系統(tǒng)AudioTrack使用方法詳解
ffmpeg開發(fā)之旅(8):Android UVC Camera(USB攝像頭)開發(fā)核心技術(shù)詳解
Android音視頻錄制與播放功能簡述
Android UCV 同時打開多路攝像頭
Android從USB聲卡錄制高質(zhì)量音頻-----使用libusb讀取USB聲卡數(shù)據(jù)
基于libusb庫、uac協(xié)議,獲取Audio聲音數(shù)據(jù)
Android/linux從usb聲卡獲取音頻(使用libusb庫)—監(jiān)聽“純麥”(五)文章來源地址http://www.zghlxwxcb.cn/news/detail-482329.html
到了這里,關(guān)于Android USBCamera投屏 - 利用UVC協(xié)議將手機(jī)上的畫面有線投屏到Android車機(jī)的屏幕上的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!