1/* 2* Copyright (C) 2023 Huawei Device Co., Ltd. 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15import common from '@ohos.app.ability.common'; 16import fs from '@ohos.file.fs'; 17import audio from '@ohos.multimedia.audio'; 18import router from '@ohos.router'; 19import resourceManager from '@ohos.resourceManager'; 20import { BusinessError } from '@ohos.base'; 21 22class Options { 23 offset: number = 0; 24 length: number = 0; 25} 26 27@Entry 28@Component 29struct PreferOutputDevice { 30 private appContext?: common.Context; 31 private audioRendererOption: audio.AudioRendererOptions = { 32 streamInfo: { 33 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, 34 channels: audio.AudioChannel.CHANNEL_2, 35 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, 36 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW 37 }, 38 rendererInfo: { 39 content: audio.ContentType.CONTENT_TYPE_SPEECH, 40 usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, 41 rendererFlags: 0 42 } 43 }; 44 private audioRenderer?: audio.AudioRenderer; 45 private audioSource = 'test1.wav'; 46 private fileDescriptor?: resourceManager.RawFileDescriptor; 47 private audioRoutingManager?: audio.AudioRoutingManager; 48 @State preferOutputDeviceName: string = ''; 49 50 async aboutToAppear(): Promise<void> { 51 this.init(); 52 } 53 54 async init(): Promise<void> { 55 if (this.appContext) { 56 return; 57 } 58 this.appContext = getContext(); 59 let audioManager = audio.getAudioManager(); 60 this.audioRoutingManager = audioManager.getRoutingManager(); 61 this.audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, async (deviceChanged) => { 62 await this.getPreferOutputDeviceForRendererInfo(); 63 }); 64 await this.getPreferOutputDeviceForRendererInfo(); 65 await this.getStageFileDescriptor(this.audioSource).then((res) => { 66 this.fileDescriptor = res; 67 }); 68 await this.renderPlay(); 69 } 70 71 async over(): Promise<void> { 72 this.appContext = undefined; 73 if (this.audioRenderer) { 74 await this.audioRenderer.release(); 75 this.audioRenderer = undefined; 76 } 77 if (this.fileDescriptor) { 78 this.closeResource(this.audioSource); 79 this.fileDescriptor = undefined; 80 } 81 this.audioRoutingManager = undefined; 82 } 83 84 onBackPress(): void { 85 this.over(); 86 } 87 88 async onPageHide(): Promise<void> { 89 this.over(); 90 } 91 92 onPageShow(): void { 93 this.init(); 94 } 95 96 async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> { 97 let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined; 98 if (this.appContext) { 99 let mgr = this.appContext.resourceManager; 100 await mgr.getRawFd(fileName).then(value => { 101 fileDescriptor = value; 102 console.log('case getRawFileDescriptor success fileName: ' + fileName); 103 }).catch((error: BusinessError) => { 104 console.log('case getRawFileDescriptor err: ' + error); 105 }); 106 } 107 return fileDescriptor; 108 } 109 110 async closeResource(fileName: string): Promise<void> { 111 if (this.appContext) { 112 let mgr = this.appContext.resourceManager; 113 await mgr.closeRawFd(fileName).then(() => { 114 console.log('case closeRawFd success fileName: ' + fileName); 115 }).catch((error: BusinessError) => { 116 console.log('case closeRawFd err: ' + error); 117 }); 118 } 119 } 120 121 getPreferOutputDeviceForRendererInfo(): void { 122 if (this.audioRoutingManager) { 123 this.audioRoutingManager.getPreferOutputDeviceForRendererInfo(this.audioRendererOption.rendererInfo) 124 .then(data => { 125 console.log(`--zhangkai--getPreferOutputDeviceForRendererInfo data:${JSON.stringify(data[0])}`); 126 this.preferOutputDeviceName = this.getDeviceList(data[0]); 127 }).catch((err: BusinessError) => { 128 this.preferOutputDeviceName = 'Invalid'; 129 console.log(`getPreferOutputDeviceForRendererInfo err:${JSON.stringify(err)}`); 130 }); 131 } 132 } 133 134 getZNDeviceTypeName(deviceTypeName: string): string { 135 if (this.appContext) { 136 let map = new Map<string, string>(); 137 map.set('EARPIECE', this.appContext.resourceManager.getStringSync($r('app.string.EarPiece').id)); 138 map.set('SPEAKER', this.appContext.resourceManager.getStringSync($r('app.string.Speaker').id)); 139 map.set('WIRED_HEADSET', this.appContext.resourceManager.getStringSync($r('app.string.WiredHeadset').id)); 140 map.set('WIRED_HEADPHONES', this.appContext.resourceManager.getStringSync($r('app.string.WiredHeadPhones').id)); 141 map.set('BLUETOOTH_A2DP', this.appContext.resourceManager.getStringSync($r('app.string.Bluetooth_A2DP').id)); 142 map.set('BLUETOOTH_SCO', this.appContext.resourceManager.getStringSync($r('app.string.BLUETOOTH_SCO').id)); 143 map.set('USB_HEADSET', this.appContext.resourceManager.getStringSync($r('app.string.USB_Headset').id)); 144 if (map.get(deviceTypeName)) { 145 return map.get(deviceTypeName) as string; 146 } else { 147 return 'Invalid'; 148 } 149 } else { 150 return ''; 151 } 152 } 153 154 getDeviceList(deviceDescriptor: audio.AudioDeviceDescriptor): string { 155 let deviceTypeName = this.getDeviceTypeNameByValue(deviceDescriptor.deviceType); 156 return this.getZNDeviceTypeName(deviceTypeName); 157 } 158 159 getDeviceTypeNameByValue(value: audio.DeviceType): string { 160 let map = new Map<number, string>(); 161 map.set(1, 'EARPIECE'); 162 map.set(2, 'SPEAKER'); 163 map.set(3, 'WIRED_HEADSET'); 164 map.set(4, 'WIRED_HEADPHONES'); 165 map.set(8, 'BLUETOOTH_A2DP'); 166 map.set(7, 'BLUETOOTH_SCO'); 167 map.set(22, 'USB_HEADSET'); 168 if (map.get(value)) { 169 return map.get(value) as string; 170 } 171 return 'Invalid'; 172 } 173 174 async renderPlay(): Promise<void> { 175 if (this.audioRenderer) { 176 return; 177 } 178 try { 179 this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption); 180 } catch (err) { 181 let error = err as BusinessError; 182 console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`); 183 return; 184 } 185 let bufferSize : number; 186 try { 187 bufferSize = await this.audioRenderer.getBufferSize(); 188 await this.audioRenderer.start(); 189 } catch (err) { 190 let error = err as BusinessError; 191 console.error(`audioRenderer start : Error: ${JSON.stringify(error)}`); 192 return; 193 } 194 195 try { 196 if (!this.fileDescriptor) { 197 return; 198 } 199 let startOffset = this.fileDescriptor.offset; 200 let cur = startOffset; 201 let buf = new ArrayBuffer(bufferSize); 202 while (true) { 203 // when render released,state is changed to STATE_RELEASED 204 if (this.audioRenderer.state === audio.AudioState.STATE_RELEASED) { 205 break; 206 } 207 while (cur <= startOffset + this.fileDescriptor.length) { 208 // when render released,state is changed to STATE_RELEASED 209 if (this.audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED.valueOf()) { 210 break; 211 } 212 let options: Options = { 213 offset: cur, 214 length: bufferSize 215 } 216 await fs.read(this.fileDescriptor.fd, buf, options); 217 await this.audioRenderer.write(buf); 218 cur += bufferSize; 219 } 220 cur = startOffset; 221 } 222 } catch (err) { 223 let error = err as BusinessError; 224 console.error(`audioRenderer write : Error: ${JSON.stringify(error)}`); 225 } 226 } 227 228 build() { 229 Column() { 230 Row() { 231 Navigation() { 232 NavRouter() { 233 NavDestination() { 234 } 235 } 236 } 237 .height('100%') 238 .width('100%') 239 .hideBackButton(false) 240 .titleMode(NavigationTitleMode.Mini) 241 .title($r('app.string.SelectOutputDevice')) 242 .mode(NavigationMode.Stack); 243 }.height(56).width('100%').id('back_btn') 244 .onClick(async () => { 245 await router.pushUrl({ url: 'pages/Index' }); 246 }); 247 248 Column() { 249 Row() { 250 Row() { 251 Image($r('app.media.ic_call')).width(48).height(48); 252 Text($r('app.string.VoiceCallType')) 253 .fontSize(16) 254 .margin({ left: 12 }) 255 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 256 .fontColor('#182431') 257 .fontWeight(500); 258 } 259 260 Row() { 261 Text(this.preferOutputDeviceName) 262 .id('device_name_text') 263 .fontFamily($r('sys.string.ohos_id_text_font_family_medium')) 264 .fontColor('#182431') 265 .fontWeight(400) 266 .opacity(0.6); 267 } 268 } 269 .justifyContent(FlexAlign.SpaceBetween) 270 .height(72) 271 .width('100%') 272 .padding({ left: '3.35%', right: '3.35%' }) 273 .backgroundColor(Color.White) 274 .borderRadius(24); 275 }.padding({ left: '3.35%', right: '3.35%' }); 276 } 277 .height('100%').width('100%').backgroundColor('#f1f3f5'); 278 } 279}