1/* 2* Copyright (C) 2024 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*/ 15 16import { audio } from '@kit.AudioKit'; 17import { fileIo } from '@kit.CoreFileKit'; 18import { BusinessError } from '@kit.BasicServicesKit'; 19import { resourceManager } from '@kit.LocalizationKit'; 20import { common } from '@kit.AbilityKit'; 21import { avSession, AVCastPicker } from '@kit.AVSessionKit'; 22 23class Options { 24 public offset: number = 0; 25 public length: number = 0; 26} 27 28@Entry 29@Component 30struct DefaultPicker { 31 @State session: avSession.AVSession | undefined = undefined; 32 @State avCastPickerColor:Color = Color.White; 33 private appContext?: common.Context | undefined = undefined; 34 private audioRenderer: audio.AudioRenderer | undefined = undefined; 35 private audioSource = 'test1.wav'; 36 private fileDescriptor?: resourceManager.RawFileDescriptor | undefined = undefined; 37 private audioManager: audio.AudioManager | undefined = undefined; 38 private audioRoutingManager: audio.AudioRoutingManager | undefined = undefined; 39 private audioRendererInfo: audio.AudioRendererInfo = { 40 usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, 41 rendererFlags: 0 42 }; 43 private audioStreamInfo: audio.AudioStreamInfo = { 44 samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率 45 channels: audio.AudioChannel.CHANNEL_2, // 通道 46 sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式 47 encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式 48 }; 49 private audioRendererOption: audio.AudioRendererOptions = { 50 streamInfo: this.audioStreamInfo, 51 rendererInfo: this.audioRendererInfo 52 }; 53 54 async aboutToAppear() { 55 console.log('about to appear'); 56 await this.init(); 57 } 58 59 async init() { 60 if (!this.appContext) { 61 this.appContext = getContext(); 62 } 63 this.session = await avSession.createAVSession(this.appContext, 'voiptest', 'voice_call'); 64 this.observerDevices(); 65 } 66 67 async observerDevices() { 68 this.audioManager = audio.getAudioManager(); 69 if (!this.audioManager) { 70 console.error('get audioManager failed'); 71 return; 72 } 73 this.audioRoutingManager = this.audioManager.getRoutingManager(); 74 if(!this.audioRoutingManager) { 75 return; 76 } 77 this.audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', this.audioRendererInfo, (desc: audio.AudioDeviceDescriptors) => { 78 console.log(`device change to: ${desc[0].deviceType}`); 79 }); 80 } 81 82 async getStageFileDescriptor(fileName: string): Promise<resourceManager.RawFileDescriptor | undefined> { 83 let fileDescriptor: resourceManager.RawFileDescriptor | undefined = undefined; 84 if (this.appContext) { 85 let mgr = this.appContext.resourceManager; 86 this.fileDescriptor = mgr.getRawFdSync(fileName); 87 await mgr.getRawFd(fileName).then(value => { 88 fileDescriptor = value; 89 console.log('case getRawFileDescriptor success fileName: ' + fileName); 90 }).catch((error: BusinessError) => { 91 console.log('case getRawFileDescriptor err: ' + error); 92 }); 93 } 94 return fileDescriptor; 95 } 96 97 async startRenderer(): Promise<void> { 98 if (this.audioRenderer !== undefined) { 99 return; 100 } 101 this.getStageFileDescriptor(this.audioSource).then((res) => { 102 this.fileDescriptor = res; 103 }); 104 if (!this.fileDescriptor) { 105 return; 106 } 107 let file: resourceManager.RawFileDescriptor = this.fileDescriptor; 108 try { 109 this.audioRenderer = await audio.createAudioRenderer(this.audioRendererOption); 110 } catch (error) { 111 console.error(`audioRenderer create : Error: ${JSON.stringify(error)}`); 112 return; 113 } 114 let bufferSize: number = this.fileDescriptor.offset; 115 let writeDataCallback = (buffer: ArrayBuffer) => { 116 let options: Options = { 117 offset: bufferSize, 118 length: buffer.byteLength 119 } 120 fileIo.readSync(file.fd, buffer, options); 121 bufferSize += buffer.byteLength; 122 }; 123 this.audioRenderer.on('writeData', writeDataCallback); 124 await this.audioRenderer.start(); 125 } 126 127 async stopRenderer(): Promise<void> { 128 if (this.audioRenderer) { 129 await this.audioRenderer.release(); 130 this.audioRenderer = undefined; 131 } 132 if (this.fileDescriptor) { 133 this.closeResource(this.audioSource); 134 this.fileDescriptor = undefined; 135 } 136 } 137 138 async closeResource(fileName: string): Promise<void> { 139 if (this.appContext) { 140 let mgr = this.appContext.resourceManager; 141 await mgr.closeRawFd(fileName).then(() => { 142 console.log('case closeRawFd success fileName: ' + fileName); 143 }).catch((error: BusinessError) => { 144 console.log('case closeRawFd err: ' + error); 145 }); 146 } 147 } 148 149 onBackPress(): void { 150 this.stopRenderer(); 151 this.destroy(); 152 } 153 154 async onPageHide(): Promise<void> { 155 this.stopRenderer(); 156 this.destroy(); 157 } 158 159 onPageShow(): void { 160 this.init(); 161 } 162 163 destroy(): void { 164 this.appContext = undefined; 165 if (this.audioRoutingManager !== undefined) { 166 this.audioRoutingManager.off('preferOutputDeviceChangeForRendererInfo'); 167 } 168 try { 169 if (this.session) { 170 this.session?.destroy(); 171 } 172 } catch (err) { 173 console.error('session destroy failed'); 174 } 175 } 176 177 aboutToDisappear() { 178 console.log('about to disappear'); 179 this.destroy(); 180 } 181 182 build() { 183 Row() { 184 Column() { 185 Button() { 186 Text('start').fontSize(22).fontColor(Color.White) 187 } 188 .size({ width: 64, height: 64 }) 189 .onClick(() => { 190 this.startRenderer(); 191 }) 192 .type(ButtonType.Circle) 193 } 194 .size({ width: '33%', height: 64 }) 195 196 Column() { 197 Button() { 198 Text('stop').fontSize(22).fontColor(Color.White) 199 } 200 .size({ width: 64, height: 64 }) 201 .onClick(() => { 202 this.stopRenderer(); 203 }) 204 .type(ButtonType.Circle) 205 } 206 .size({ width: '33%', height: 64 }) 207 208 Column() { 209 Button() { 210 AVCastPicker({ 211 normalColor: this.avCastPickerColor, 212 activeColor: this.avCastPickerColor, 213 }) 214 .size({ width: 45, height: 45 }) 215 } 216 .size({ width: 64, height: 64 }) 217 .type(ButtonType.Circle) 218 } 219 .size({ width: '33%', height: 64 }) 220 } 221 .margin({ top: 300}) 222 .justifyContent(FlexAlign.SpaceBetween) 223 } 224}