audio.service.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { Injectable } from '@angular/core';
  2. import { Capacitor } from '@capacitor/core';
  3. import { Filesystem, Directory } from '@capacitor/filesystem';
  4. @Injectable({
  5. providedIn: 'root'
  6. })
  7. export class AudioService {
  8. private audioContext!: AudioContext;
  9. private mediaRecorder!: MediaRecorder;
  10. private audioChunks: Blob[] = [];
  11. private audioAnalyser!: AnalyserNode;
  12. private microphone!: MediaStreamAudioSourceNode;
  13. private scriptProcessor!: ScriptProcessorNode;
  14. private volumeCallback!: (volume: number) => void;
  15. private isRecording = false;
  16. constructor() {}
  17. initAudioContext() {
  18. if (!this.audioContext) {
  19. this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
  20. }
  21. }
  22. async startRecording(volumeCallback: (volume: number) => void): Promise<void> {
  23. this.volumeCallback = volumeCallback;
  24. this.audioChunks = [];
  25. try {
  26. const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
  27. this.mediaRecorder = new MediaRecorder(stream);
  28. // 设置音频分析
  29. this.setupAudioAnalysis(stream);
  30. this.mediaRecorder.ondataavailable = (event) => {
  31. this.audioChunks.push(event.data);
  32. };
  33. this.mediaRecorder.start(100); // 每100ms收集一次数据
  34. this.isRecording = true;
  35. } catch (error) {
  36. console.error('无法访问麦克风:', error);
  37. throw error;
  38. }
  39. }
  40. private setupAudioAnalysis(stream: MediaStream) {
  41. this.microphone = this.audioContext.createMediaStreamSource(stream);
  42. this.audioAnalyser = this.audioContext.createAnalyser();
  43. this.audioAnalyser.fftSize = 256;
  44. this.scriptProcessor = this.audioContext.createScriptProcessor(2048, 1, 1);
  45. this.microphone.connect(this.audioAnalyser);
  46. this.audioAnalyser.connect(this.scriptProcessor);
  47. this.scriptProcessor.connect(this.audioContext.destination);
  48. const that = this;
  49. this.scriptProcessor.onaudioprocess = function() {
  50. if (!that.isRecording) return;
  51. const array = new Uint8Array(that.audioAnalyser.frequencyBinCount);
  52. that.audioAnalyser.getByteFrequencyData(array);
  53. let values = 0;
  54. const length = array.length;
  55. for (let i = 0; i < length; i++) {
  56. values += array[i];
  57. }
  58. const average = values / length;
  59. that.volumeCallback(average);
  60. };
  61. }
  62. async stopRecording(): Promise<Blob> {
  63. return new Promise((resolve) => {
  64. this.mediaRecorder.onstop = () => {
  65. const audioBlob = new Blob(this.audioChunks, { type: 'audio/wav' });
  66. resolve(audioBlob);
  67. };
  68. this.mediaRecorder.stop();
  69. this.cleanupAudioNodes();
  70. this.isRecording = false;
  71. });
  72. }
  73. getRecordedChunks(): Blob[] {
  74. return this.audioChunks;
  75. }
  76. analyzeAudio(audioChunks: Blob[], callback: (dataArray: Uint8Array) => void) {
  77. const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
  78. const fileReader = new FileReader();
  79. fileReader.onload = () => {
  80. const arrayBuffer = fileReader.result as ArrayBuffer;
  81. this.audioContext.decodeAudioData(arrayBuffer).then((audioBuffer) => {
  82. const offlineContext = new OfflineAudioContext(
  83. audioBuffer.numberOfChannels,
  84. audioBuffer.length,
  85. audioBuffer.sampleRate
  86. );
  87. const source = offlineContext.createBufferSource();
  88. source.buffer = audioBuffer;
  89. const analyser = offlineContext.createAnalyser();
  90. analyser.fftSize = 2048;
  91. source.connect(analyser);
  92. analyser.connect(offlineContext.destination);
  93. source.start(0);
  94. offlineContext.startRendering().then(() => {
  95. const bufferLength = analyser.frequencyBinCount;
  96. const dataArray = new Uint8Array(bufferLength);
  97. analyser.getByteTimeDomainData(dataArray);
  98. callback(dataArray);
  99. });
  100. });
  101. };
  102. fileReader.readAsArrayBuffer(audioBlob);
  103. }
  104. private cleanupAudioNodes() {
  105. if (this.scriptProcessor) {
  106. this.scriptProcessor.disconnect();
  107. this.scriptProcessor.onaudioprocess = null;
  108. }
  109. if (this.microphone) {
  110. this.microphone.disconnect();
  111. }
  112. if (this.mediaRecorder && this.mediaRecorder.stream) {
  113. this.mediaRecorder.stream.getTracks().forEach(track => track.stop());
  114. }
  115. }
  116. }