Я записал образцы голоса с микрофона, используя Adobe Flash Builder 4.6 / AIR, записанный голос. Сначала я преобразовал речевые данные (байтовый массив) в формат base64 в actionscript, после чего я преобразовал данные base64 в WAV-файл, используя свой PHP-код. но этот файл WAV-файла искал файл в RiffPad.
RIFFPad является средством просмотра файлов в формате RIFF, таких как WAV, AVI.
ожидаемая спецификация файла wav:
выборка Оценка : 22KHZ
// -- saves the current audio data as a .wav file protected function onSubmit( event:Event ):void { alertBox.show("Processing ... please wait."); stopPlayback(); stopRecording(); playBtn.enabled = recordBtn.enabled = submitBtn.enabled = false; var position:int = capture.buffer.position; var wavWriter:WAVWriter = new WAVWriter() var wavWriter1:WaveEncoder = new WaveEncoder() wavWriter.numOfChannels = 1; wavWriter.samplingRate = 22050; wavWriter.sampleBitRate = 16; var wavBytes:ByteArray = new ByteArray; capture.buffer.position = 0; wavWriter.processSamples(wavBytes, capture.buffer, capture.microphone.rate * 1000, 1); Settings.alertBox3.show("RATE :"+capture.microphone.rate); //Here show RATE: 8 //wavWriter.processSamples(wavBytes, capture.buffer, 22050, 1); //wavBytes = wavWriter1.encode( capture.buffer, 1, 16, 22050); capture.buffer.position = position; wavBytes.position=0; submitVoiceSample(Base64_new.encodeByteArray(wavBytes)); }
Функция заголовка WAV Writer:
public var samplingRate = 22050; public var sampleBitRate:int = 8; public var numOfChannels:int = 2; private var compressionCode:int = 1; private function header(dataOutput:IDataOutput, fileSize:Number):void { dataOutput.writeUTFBytes("RIFF"); dataOutput.writeUnsignedInt(fileSize); // Size of whole file dataOutput.writeUTFBytes("WAVE"); // WAVE Chunk dataOutput.writeUTFBytes("fmt "); // Chunk ID dataOutput.writeUnsignedInt(16); // Header Chunk Data Size dataOutput.writeShort(compressionCode); // Compression code - 1 = PCM dataOutput.writeShort(numOfChannels); // Number of channels dataOutput.writeUnsignedInt(samplingRate); // Sample rate dataOutput.writeUnsignedInt(samplingRate * numOfChannels * sampleBitRate / 8); // Byte Rate == SampleRate * NumChannels * BitsPerSample/8 dataOutput.writeShort(numOfChannels * sampleBitRate / 8); // Block align == NumChannels * BitsPerSample/8 dataOutput.writeShort(sampleBitRate); // Bits Per Sample }
WAV-файл Функция Writer:
public function processSamples(dataOutput:IDataOutput, dataInput:ByteArray, inputSamplingRate:int, inputNumChannels:int = 1):void { if (!dataInput || dataInput.bytesAvailable <= 0) // Return if null throw new Error("No audio data"); // 16 bit values are between -32768 to 32767. var bitResolution:Number = (Math.pow(2, sampleBitRate)/2)-1; var soundRate:Number = samplingRate / inputSamplingRate; var dataByteLength:int = ((dataInput.length/4) * soundRate * sampleBitRate/8); // data.length is in 4 bytes per float, where we want samples * sampleBitRate/8 for bytes //var fileSize:int = 32 + 8 + dataByteLength; var fileSize:int = 32 + 4 + dataByteLength; // WAV format requires little-endian dataOutput.endian = Endian.LITTLE_ENDIAN; // RIFF WAVE Header Information header(dataOutput, fileSize); // Data Chunk Header dataOutput.writeUTFBytes("data"); dataOutput.writeUnsignedInt(dataByteLength); // Size of whole file // Write data to file dataInput.position = 0; var tempData:ByteArray = new ByteArray(); tempData.endian = Endian.LITTLE_ENDIAN; // Write to file in chunks of converted data. while (dataInput.bytesAvailable > 0) { tempData.clear(); // Resampling logic variables var minSamples:int = Math.min(dataInput.bytesAvailable/4, 8192); var readSampleLength:int = minSamples;//Math.floor(minSamples/soundRate); var resampleFrequency:int = 100; // Every X frames drop or add frames var resampleFrequencyCheck:int = (soundRate-Math.floor(soundRate))*resampleFrequency; var soundRateCeil:int = Math.ceil(soundRate); var soundRateFloor:int = Math.floor(soundRate); var jlen:int = 0; var channelCount:int = (numOfChannels-inputNumChannels); /* trace("resampleFrequency: " + resampleFrequency + " resampleFrequencyCheck: " + resampleFrequencyCheck + " soundRateCeil: " + soundRateCeil + " soundRateFloor: " + soundRateFloor); */ var value:Number = 0; // Assumes data is in samples of float value for (var i:int = 0;i < readSampleLength;i+=4) { value = dataInput.readFloat(); // Check for sanity of float value if (value > 1 || value < -1) throw new Error("Audio samples not in float format"); // Special case with 8bit WAV files if (sampleBitRate == 8) value = (bitResolution * value) + bitResolution; else value = bitResolution * value; // Resampling Logic for non-integer sampling rate conversions jlen = (resampleFrequencyCheck > 0 && i % resampleFrequency < resampleFrequencyCheck) ? soundRateCeil : soundRateFloor; for (var j:int = 0; j < jlen; j++) { writeCorrectBits(tempData, value, channelCount); } } dataOutput.writeBytes(tempData); } }
Я отправляю эти данные base64 на сервер php сторон службы запроса, я получил параметр $ this-> request-> voiceSample и декодировал base64 в WAV-файл
file_put_contents('name.wav', base64_decode($this->request->voiceSample));
После загрузки этого файла «name.wav» в Riffpad у меня возникла проблема
В конце файла есть лишний барахл.
Любой, пожалуйста, дайте мне совет, чтобы решить этот вопрос …
В этой строке есть неотъемлемая ошибка:
wavWriter.processSamples(wavBytes, capture.buffer, capture.microphone.rate * 1000, 1);
В руководстве Microphone.rate
указано, что фактическая частота дискретизации отличается от microphone.rate*1000
как и ожидалось этим кодом. Фактическая таблица выглядит следующим образом:
rate Actual frequency 44 44,100 Hz 22 22,050 Hz 11 11,025 Hz 8 8,000 Hz 5 5,512 Hz
Итак, в то время как ваши комментарии к коду указывают, что rate
сообщается как 8, это может быть не так на стороне клиента в целом, поэтому выполните поиск до передачи wavWriter.processSamples()
частоты дискретизации в wavWriter.processSamples()
.
Затем вы предварительно вычисляете dataByteLength
помощью вычисления с плавающей запятой, это может оказаться неточным, так как тогда вы производите байты данных байтом, поэтому лучше сначала dataOutput
, затем собрать длину данных и только затем записать все данные в dataOutput
, как это :
public function processSamples(dataOutput:IDataOutput, dataInput:ByteArray, inputSamplingRate:int, inputNumChannels:int = 1):void { if (!dataInput || dataInput.bytesAvailable <= 0) // Return if null throw new Error("No audio data"); // 16 bit values are between -32768 to 32767. var bitResolution:Number = (Math.pow(2, sampleBitRate)/2)-1; // var soundRate:Number = samplingRate / inputSamplingRate; // var fileSize:int = 32 + 4 + dataByteLength; kept for reference // fmt tag is 4+4+16, data header is 8 bytes in size, and 4 bytes for WAVE // but the data length is not yet determined // WAV format requires little-endian dataOutput.endian = Endian.LITTLE_ENDIAN; // Prepare data for data to file dataInput.position = 0; var tempData:ByteArray = new ByteArray(); tempData.endian = Endian.LITTLE_ENDIAN; // Writing in chunks is no longer possible, because we don't have the header ready // Let's precalculate the data needed in the loop var step:Number=inputSamplingRate / samplingRate; // how far we should step into the input data to get next sample var totalOffset:Number=1.0-1e-8; // accumulator for step var oldChannels:Array=[]; var i:int; for (i=0;i<numOfChannels;i++) oldChannels.push(0.0); // previous channels' sample holder var newChannels:Array=oldChannels.slice(); // same for new channels that are to be read from byte array // reading first sample set from input byte array if (dataInput.bytesAvailable>=inputNumChannels*4) { for (i=0;i<inputNumChannels;i++) { var buf:Number=dataInput.readFloat(); if (buf > 1) buf=1; if (buf < -1) buf=-1; newChannels[i]=buf; } // if there's one channel, copy data to other channels if ((inputNumChannels==1) && (numOfChannels>1)) { for (i=1;i<numOfChannels;i++) newChannels[i]=newChannels[0]; } } while ((dataInput.bytesAvailable>=inputNumChannels*4) || (totalOffset<1.0)) { // sample next value for output wave file var value:Number; for (i=0;i<numOfChannels;i++) { value = (totalOffset*newChannels[i])+(1.0-totalOffset)*oldChannels[i]; // linear interpolation between old sample and new sample // Special case with 8bit WAV files if (sampleBitRate == 8) value = (bitResolution * value) + bitResolution; else value = bitResolution * value; // writing one channel into tempData writeCorrectBits(tempData, value, 0); } totalOffset+=step; // advance per output sample while ((totalOffset>1) && (dataInput.bytesAvailable>=inputNumChannels*4)) { // we need a new sample, and have a sample to process in input totalOffset-=1; for (i=0;i<numOfChannels;i++) oldChannels[i]=newChannels[i]; // store old sample // get another sample, copypasted from above for (i=0;i<inputNumChannels;i++) { value=dataInput.readFloat(); if (value > 1) value=1; if (value < -1) value=-1; // sanity check // I made it clip instead of throwing exception, replace if necessary // if (value > 1 || value < -1) throw new Error("Audio samples not in float format"); newChannels[i]=value; } if ((inputNumChannels==1) && (numOfChannels>1)) { for (i=1;i<numOfChannels;i++) newChannels[i]=newChannels[0]; } } // end advance by totalOffset } // end main loop var dataBytesLength:uint=tempData.length; // now the length will be correct by definition header(dataOutput, 32+4+dataBytesLength); dataOutput.writeUTFBytes("data"); dataOutput.writeUnsignedInt(dataBytesLength); dataOutput.writeBytes(tempData); }
Я переписал процедуру resample для использования алгоритма скользящего окна (лучше всего работает, если новая частота дискретизации выше старой, но принимает любое соотношение). Этот алгоритм использует линейную интерполяцию между выборками вместо простого повторного использования старого образца по длине интерполированной последовательности. Не стесняйтесь заменять своим собственным циклом. Принцип, который следует сохранить, состоит в том, что вы сначала компилируете полные tempData
и только затем записываете заголовок с теперь правильно определенной длиной данных.
Пожалуйста, сообщите о проблемах, если они есть.