Actionscript 中的录音文件损坏问题 [英] Audio recorded file corrupted issue in Actionscript
问题描述
我使用 Adobe Flash Builder 4.6/AIR 从我的麦克风录制了语音样本,成功录制了语音.我首先在 actionscript 中将语音数据(字节数组)转换为 base64 格式,然后使用我的 PHP 代码将该 base64 数据转换为 WAV 文件.但是那个 WAV 文件在 RiffPad 中抛出文件损坏的问题.
I recorded voice samples from my microphone using Adobe Flash Builder 4.6 / AIR, voice recorded successfully. I first converted voice data(byte array) to base64 format in actionscript then I converted that base64 data to WAV file using my PHP code. but that WAV file throw file corrupted issue in RiffPad.
RIFFPad 是一个查看 RIFF 格式文件(如 WAV、AVI)的查看器.
RIFFPad is a viewer for RIFF formatted files like 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 文件写入功能:
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 文件
I send that base64 data to my service request php side i got the '$this->request->voiceSample' parameter and decode base64 to .wav file
file_put_contents('name.wav', base64_decode($this->request->voiceSample));
在 Riffpad 中加载name.wav"文件后我有问题
文件末尾有多余的垃圾.
There is extra junk at the end of the file.
任何人请给我解决这个问题的建议...
Any one please give me the advice to solve this issue...
推荐答案
这行有一个固有的错误:
There is an inherent mistake in this line:
wavWriter.processSamples(wavBytes, capture.buffer, capture.microphone.rate * 1000, 1);
麦克风.rate
手册指出实际采样频率与此代码预期的 microphone.rate*1000
不同.实际表格如下:
The Microphone.rate
manual states that actual sampling frequency differs from microphone.rate*1000
as expected by this code. The actual table is as follows:
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,但在客户端通常情况并非如此,因此在将推导出的采样率传递给 rate
之前执行查找代码>wavWriter.processSamples().
So, while your code comments state that rate
is reported as 8, this might not be the case on the client side in general, so perform the lookup prior to passing the deduced sampling rate into wavWriter.processSamples()
.
接下来,您正在通过浮点计算预先计算 dataByteLength
,这可能最终不准确,因为您然后逐字节采样数据,因此最好先重新采样,然后收集数据长度,并且仅然后将所有数据写入dataOutput
,像这样:
Next, you are precalculating dataByteLength
via floating point calculation, this might end up being inaccurate as you then sample the data byte by byte, so it's better to first resample, then gather data length and only then write all the data into dataOutput
, like this:
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);
}
我已经重写了重新采样例程以使用滑动窗口算法(如果新采样率高于旧采样率,则效果最佳,但接受任何比率).该算法在样本之间使用线性插值,而不是在插值序列的长度上简单地重新使用旧样本.随意替换为您自己的循环.应该保留的原则是您首先编译full tempData
,然后才使用现在正确定义的数据长度写入标头.
I have rewritten the resample routine to use sliding window algorithm (works best if new sample rate is higher than old, but accepts any ratio). This algorithm uses linear interpolation between samples instead of plainly re-using old sample over the length of the interpolated sequence. Feel free to replace with your own loop. The principal that should be retained is that you first compile full tempData
and only then write the header with now correctly defined data length.
如果有问题请报告.
这篇关于Actionscript 中的录音文件损坏问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!