/* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "webrtc/modules/media_file/media_file_utility.h" #include #include #include #include #include "webrtc/base/format_macros.h" #include "webrtc/common_audio/wav_header.h" #include "webrtc/common_types.h" #include "webrtc/modules/include/module_common_types.h" #include "webrtc/system_wrappers/include/file_wrapper.h" #include "webrtc/system_wrappers/include/trace.h" #include "webrtc/voice_engine_configurations.h" namespace { // First 16 bytes the WAVE header. ckID should be "RIFF", wave_ckID should be // "WAVE" and ckSize is the chunk size (4 + n) struct WAVE_RIFF_header { int8_t ckID[4]; int32_t ckSize; int8_t wave_ckID[4]; }; // First 8 byte of the format chunk. fmt_ckID should be "fmt ". fmt_ckSize is // the chunk size (16, 18 or 40 byte) struct WAVE_CHUNK_header { int8_t fmt_ckID[4]; uint32_t fmt_ckSize; }; } // unnamed namespace namespace webrtc { ModuleFileUtility::ModuleFileUtility(const int32_t id) : _wavFormatObj(), _dataSize(0), _readSizeBytes(0), _id(id), _stopPointInMs(0), _startPointInMs(0), _playoutPositionMs(0), _bytesWritten(0), codec_info_(), _codecId(kCodecNoCodec), _bytesPerSample(0), _readPos(0), _reading(false), _writing(false), _tempData() { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "ModuleFileUtility::ModuleFileUtility()"); memset(&codec_info_,0,sizeof(CodecInst)); codec_info_.pltype = -1; } ModuleFileUtility::~ModuleFileUtility() { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "ModuleFileUtility::~ModuleFileUtility()"); } int32_t ModuleFileUtility::ReadWavHeader(InStream& wav) { WAVE_RIFF_header RIFFheaderObj; WAVE_CHUNK_header CHUNKheaderObj; // TODO (hellner): tmpStr and tmpStr2 seems unnecessary here. char tmpStr[6] = "FOUR"; unsigned char tmpStr2[4]; size_t i; bool dataFound = false; bool fmtFound = false; int8_t dummyRead; _dataSize = 0; int len = wav.Read(&RIFFheaderObj, sizeof(WAVE_RIFF_header)); if (len != static_cast(sizeof(WAVE_RIFF_header))) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a wave file (too short)"); return -1; } for (i = 0; i < 4; i++) { tmpStr[i] = RIFFheaderObj.ckID[i]; } if(strcmp(tmpStr, "RIFF") != 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a wave file (does not have RIFF)"); return -1; } for (i = 0; i < 4; i++) { tmpStr[i] = RIFFheaderObj.wave_ckID[i]; } if(strcmp(tmpStr, "WAVE") != 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Not a wave file (does not have WAVE)"); return -1; } len = wav.Read(&CHUNKheaderObj, sizeof(WAVE_CHUNK_header)); // WAVE files are stored in little endian byte order. Make sure that the // data can be read on big endian as well. // TODO (hellner): little endian to system byte order should be done in // in a subroutine. memcpy(tmpStr2, &CHUNKheaderObj.fmt_ckSize, 4); CHUNKheaderObj.fmt_ckSize = (uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1]) << 8) + (((uint32_t)tmpStr2[2]) << 16) + (((uint32_t)tmpStr2[3]) << 24); memcpy(tmpStr, CHUNKheaderObj.fmt_ckID, 4); while ((len == static_cast(sizeof(WAVE_CHUNK_header))) && (!fmtFound || !dataFound)) { if(strcmp(tmpStr, "fmt ") == 0) { len = wav.Read(&_wavFormatObj, sizeof(WAVE_FMTINFO_header)); memcpy(tmpStr2, &_wavFormatObj.formatTag, 2); _wavFormatObj.formatTag = (uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8); memcpy(tmpStr2, &_wavFormatObj.nChannels, 2); _wavFormatObj.nChannels = (int16_t) ((uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8)); memcpy(tmpStr2, &_wavFormatObj.nSamplesPerSec, 4); _wavFormatObj.nSamplesPerSec = (int32_t) ((uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8) + (((uint32_t)tmpStr2[2])<<16) + (((uint32_t)tmpStr2[3])<<24)); memcpy(tmpStr2, &_wavFormatObj.nAvgBytesPerSec, 4); _wavFormatObj.nAvgBytesPerSec = (int32_t) ((uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8) + (((uint32_t)tmpStr2[2])<<16) + (((uint32_t)tmpStr2[3])<<24)); memcpy(tmpStr2, &_wavFormatObj.nBlockAlign, 2); _wavFormatObj.nBlockAlign = (int16_t) ((uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8)); memcpy(tmpStr2, &_wavFormatObj.nBitsPerSample, 2); _wavFormatObj.nBitsPerSample = (int16_t) ((uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1])<<8)); if (CHUNKheaderObj.fmt_ckSize < sizeof(WAVE_FMTINFO_header)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Chunk size is too small"); return -1; } for (i = 0; i < CHUNKheaderObj.fmt_ckSize - sizeof(WAVE_FMTINFO_header); i++) { len = wav.Read(&dummyRead, 1); if(len != 1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "File corrupted, reached EOF (reading fmt)"); return -1; } } fmtFound = true; } else if(strcmp(tmpStr, "data") == 0) { _dataSize = CHUNKheaderObj.fmt_ckSize; dataFound = true; break; } else { for (i = 0; i < CHUNKheaderObj.fmt_ckSize; i++) { len = wav.Read(&dummyRead, 1); if(len != 1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "File corrupted, reached EOF (reading other)"); return -1; } } } len = wav.Read(&CHUNKheaderObj, sizeof(WAVE_CHUNK_header)); memcpy(tmpStr2, &CHUNKheaderObj.fmt_ckSize, 4); CHUNKheaderObj.fmt_ckSize = (uint32_t)tmpStr2[0] + (((uint32_t)tmpStr2[1]) << 8) + (((uint32_t)tmpStr2[2]) << 16) + (((uint32_t)tmpStr2[3]) << 24); memcpy(tmpStr, CHUNKheaderObj.fmt_ckID, 4); } // Either a proper format chunk has been read or a data chunk was come // across. if( (_wavFormatObj.formatTag != kWavFormatPcm) && (_wavFormatObj.formatTag != kWavFormatALaw) && (_wavFormatObj.formatTag != kWavFormatMuLaw)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Coding formatTag value=%d not supported!", _wavFormatObj.formatTag); return -1; } if((_wavFormatObj.nChannels < 1) || (_wavFormatObj.nChannels > 2)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "nChannels value=%d not supported!", _wavFormatObj.nChannels); return -1; } if((_wavFormatObj.nBitsPerSample != 8) && (_wavFormatObj.nBitsPerSample != 16)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "nBitsPerSample value=%d not supported!", _wavFormatObj.nBitsPerSample); return -1; } // Calculate the number of bytes that 10 ms of audio data correspond to. size_t samples_per_10ms = ((_wavFormatObj.formatTag == kWavFormatPcm) && (_wavFormatObj.nSamplesPerSec == 44100)) ? 440 : static_cast(_wavFormatObj.nSamplesPerSec / 100); _readSizeBytes = samples_per_10ms * _wavFormatObj.nChannels * (_wavFormatObj.nBitsPerSample / 8); return 0; } int32_t ModuleFileUtility::InitWavCodec(uint32_t samplesPerSec, size_t channels, uint32_t bitsPerSample, uint32_t formatTag) { codec_info_.pltype = -1; codec_info_.plfreq = samplesPerSec; codec_info_.channels = channels; codec_info_.rate = bitsPerSample * samplesPerSec; // Calculate the packet size for 10ms frames switch(formatTag) { case kWavFormatALaw: strcpy(codec_info_.plname, "PCMA"); _codecId = kCodecPcma; codec_info_.pltype = 8; codec_info_.pacsize = codec_info_.plfreq / 100; break; case kWavFormatMuLaw: strcpy(codec_info_.plname, "PCMU"); _codecId = kCodecPcmu; codec_info_.pltype = 0; codec_info_.pacsize = codec_info_.plfreq / 100; break; case kWavFormatPcm: codec_info_.pacsize = (bitsPerSample * (codec_info_.plfreq / 100)) / 8; if(samplesPerSec == 8000) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_8Khz; } else if(samplesPerSec == 16000) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_16kHz; } else if(samplesPerSec == 32000) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_32Khz; } // Set the packet size for "odd" sampling frequencies so that it // properly corresponds to _readSizeBytes. else if(samplesPerSec == 11025) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_16kHz; codec_info_.pacsize = 110; codec_info_.plfreq = 11000; } else if(samplesPerSec == 22050) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_16kHz; codec_info_.pacsize = 220; codec_info_.plfreq = 22000; } else if(samplesPerSec == 44100) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_16kHz; codec_info_.pacsize = 440; codec_info_.plfreq = 44000; } else if(samplesPerSec == 48000) { strcpy(codec_info_.plname, "L16"); _codecId = kCodecL16_16kHz; codec_info_.pacsize = 480; codec_info_.plfreq = 48000; } else { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Unsupported PCM frequency!"); return -1; } break; default: WEBRTC_TRACE(kTraceError, kTraceFile, _id, "unknown WAV format TAG!"); return -1; break; } return 0; } int32_t ModuleFileUtility::InitWavReading(InStream& wav, const uint32_t start, const uint32_t stop) { _reading = false; if(ReadWavHeader(wav) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "failed to read WAV header!"); return -1; } _playoutPositionMs = 0; _readPos = 0; if(start > 0) { uint8_t dummy[WAV_MAX_BUFFER_SIZE]; int readLength; if(_readSizeBytes <= WAV_MAX_BUFFER_SIZE) { while (_playoutPositionMs < start) { readLength = wav.Read(dummy, _readSizeBytes); if(readLength == static_cast(_readSizeBytes)) { _readPos += _readSizeBytes; _playoutPositionMs += 10; } else // Must have reached EOF before start position! { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "InitWavReading(), EOF before start position"); return -1; } } } else { return -1; } } if( InitWavCodec(_wavFormatObj.nSamplesPerSec, _wavFormatObj.nChannels, _wavFormatObj.nBitsPerSample, _wavFormatObj.formatTag) != 0) { return -1; } _bytesPerSample = static_cast(_wavFormatObj.nBitsPerSample / 8); _startPointInMs = start; _stopPointInMs = stop; _reading = true; return 0; } int32_t ModuleFileUtility::ReadWavDataAsMono( InStream& wav, int8_t* outData, const size_t bufferSize) { WEBRTC_TRACE( kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadWavDataAsMono(wav= 0x%x, outData= 0x%d, " "bufSize= %" PRIuS ")", &wav, outData, bufferSize); // The number of bytes that should be read from file. const size_t totalBytesNeeded = _readSizeBytes; // The number of bytes that will be written to outData. const size_t bytesRequested = (codec_info_.channels == 2) ? totalBytesNeeded >> 1 : totalBytesNeeded; if(bufferSize < bytesRequested) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: output buffer is too short!"); return -1; } if(outData == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: output buffer NULL!"); return -1; } if(!_reading) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: no longer reading file."); return -1; } int32_t bytesRead = ReadWavData( wav, (codec_info_.channels == 2) ? _tempData : (uint8_t*)outData, totalBytesNeeded); if(bytesRead == 0) { return 0; } if(bytesRead < 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: failed to read data from WAV file."); return -1; } // Output data is should be mono. if(codec_info_.channels == 2) { for (size_t i = 0; i < bytesRequested / _bytesPerSample; i++) { // Sample value is the average of left and right buffer rounded to // closest integer value. Note samples can be either 1 or 2 byte. if(_bytesPerSample == 1) { _tempData[i] = ((_tempData[2 * i] + _tempData[(2 * i) + 1] + 1) >> 1); } else { int16_t* sampleData = (int16_t*) _tempData; sampleData[i] = ((sampleData[2 * i] + sampleData[(2 * i) + 1] + 1) >> 1); } } memcpy(outData, _tempData, bytesRequested); } return static_cast(bytesRequested); } int32_t ModuleFileUtility::ReadWavDataAsStereo( InStream& wav, int8_t* outDataLeft, int8_t* outDataRight, const size_t bufferSize) { WEBRTC_TRACE( kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadWavDataAsStereo(wav= 0x%x, outLeft= 0x%x, " "outRight= 0x%x, bufSize= %" PRIuS ")", &wav, outDataLeft, outDataRight, bufferSize); if((outDataLeft == NULL) || (outDataRight == NULL)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: an input buffer is NULL!"); return -1; } if(codec_info_.channels != 2) { WEBRTC_TRACE( kTraceError, kTraceFile, _id, "ReadWavDataAsStereo: WAV file does not contain stereo data!"); return -1; } if(! _reading) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsStereo: no longer reading file."); return -1; } // The number of bytes that should be read from file. const size_t totalBytesNeeded = _readSizeBytes; // The number of bytes that will be written to the left and the right // buffers. const size_t bytesRequested = totalBytesNeeded >> 1; if(bufferSize < bytesRequested) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavData: Output buffers are too short!"); assert(false); return -1; } int32_t bytesRead = ReadWavData(wav, _tempData, totalBytesNeeded); if(bytesRead <= 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsStereo: failed to read data from WAV file."); return -1; } // Turn interleaved audio to left and right buffer. Note samples can be // either 1 or 2 bytes if(_bytesPerSample == 1) { for (size_t i = 0; i < bytesRequested; i++) { outDataLeft[i] = _tempData[2 * i]; outDataRight[i] = _tempData[(2 * i) + 1]; } } else if(_bytesPerSample == 2) { int16_t* sampleData = reinterpret_cast(_tempData); int16_t* outLeft = reinterpret_cast(outDataLeft); int16_t* outRight = reinterpret_cast( outDataRight); // Bytes requested to samples requested. size_t sampleCount = bytesRequested >> 1; for (size_t i = 0; i < sampleCount; i++) { outLeft[i] = sampleData[2 * i]; outRight[i] = sampleData[(2 * i) + 1]; } } else { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavStereoData: unsupported sample size %" PRIuS "!", _bytesPerSample); assert(false); return -1; } return static_cast(bytesRequested); } int32_t ModuleFileUtility::ReadWavData(InStream& wav, uint8_t* buffer, size_t dataLengthInBytes) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadWavData(wav= 0x%x, buffer= 0x%x, " "dataLen= %" PRIuS ")", &wav, buffer, dataLengthInBytes); if(buffer == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadWavDataAsMono: output buffer NULL!"); return -1; } // Make sure that a read won't return too few samples. // TODO (hellner): why not read the remaining bytes needed from the start // of the file? if(_dataSize < (_readPos + dataLengthInBytes)) { // Rewind() being -1 may be due to the file not supposed to be looped. if(wav.Rewind() == -1) { _reading = false; return 0; } if(InitWavReading(wav, _startPointInMs, _stopPointInMs) == -1) { _reading = false; return -1; } } int32_t bytesRead = wav.Read(buffer, dataLengthInBytes); if(bytesRead < 0) { _reading = false; return -1; } // This should never happen due to earlier sanity checks. // TODO (hellner): change to an assert and fail here since this should // never happen... if(bytesRead < (int32_t)dataLengthInBytes) { if((wav.Rewind() == -1) || (InitWavReading(wav, _startPointInMs, _stopPointInMs) == -1)) { _reading = false; return -1; } else { bytesRead = wav.Read(buffer, dataLengthInBytes); if(bytesRead < (int32_t)dataLengthInBytes) { _reading = false; return -1; } } } _readPos += bytesRead; // TODO (hellner): Why is dataLengthInBytes let dictate the number of bytes // to read when exactly 10ms should be read?! _playoutPositionMs += 10; if((_stopPointInMs > 0) && (_playoutPositionMs >= _stopPointInMs)) { if((wav.Rewind() == -1) || (InitWavReading(wav, _startPointInMs, _stopPointInMs) == -1)) { _reading = false; } } return bytesRead; } int32_t ModuleFileUtility::InitWavWriting(OutStream& wav, const CodecInst& codecInst) { if(set_codec_info(codecInst) != 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "codecInst identifies unsupported codec!"); return -1; } _writing = false; size_t channels = (codecInst.channels == 0) ? 1 : codecInst.channels; if(STR_CASE_CMP(codecInst.plname, "PCMU") == 0) { _bytesPerSample = 1; if(WriteWavHeader(wav, 8000, _bytesPerSample, channels, kWavFormatMuLaw, 0) == -1) { return -1; } } else if(STR_CASE_CMP(codecInst.plname, "PCMA") == 0) { _bytesPerSample = 1; if(WriteWavHeader(wav, 8000, _bytesPerSample, channels, kWavFormatALaw, 0) == -1) { return -1; } } else if(STR_CASE_CMP(codecInst.plname, "L16") == 0) { _bytesPerSample = 2; if(WriteWavHeader(wav, codecInst.plfreq, _bytesPerSample, channels, kWavFormatPcm, 0) == -1) { return -1; } } else { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "codecInst identifies unsupported codec for WAV file!"); return -1; } _writing = true; _bytesWritten = 0; return 0; } int32_t ModuleFileUtility::WriteWavData(OutStream& out, const int8_t* buffer, const size_t dataLength) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::WriteWavData(out= 0x%x, buf= 0x%x, " "dataLen= %" PRIuS ")", &out, buffer, dataLength); if(buffer == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "WriteWavData: input buffer NULL!"); return -1; } if(!out.Write(buffer, dataLength)) { return -1; } _bytesWritten += dataLength; return static_cast(dataLength); } int32_t ModuleFileUtility::WriteWavHeader( OutStream& wav, uint32_t freq, size_t bytesPerSample, size_t channels, uint32_t format, size_t lengthInBytes) { // Frame size in bytes for 10 ms of audio. // TODO (hellner): 44.1 kHz has 440 samples frame size. Doesn't seem to // be taken into consideration here! const size_t frameSize = (freq / 100) * channels; // Calculate the number of full frames that the wave file contain. const size_t dataLengthInBytes = frameSize * (lengthInBytes / frameSize); uint8_t buf[kWavHeaderSize]; webrtc::WriteWavHeader(buf, channels, freq, static_cast(format), bytesPerSample, dataLengthInBytes / bytesPerSample); wav.Write(buf, kWavHeaderSize); return 0; } int32_t ModuleFileUtility::UpdateWavHeader(OutStream& wav) { int32_t res = -1; if(wav.Rewind() == -1) { return -1; } size_t channels = (codec_info_.channels == 0) ? 1 : codec_info_.channels; if(STR_CASE_CMP(codec_info_.plname, "L16") == 0) { res = WriteWavHeader(wav, codec_info_.plfreq, 2, channels, kWavFormatPcm, _bytesWritten); } else if(STR_CASE_CMP(codec_info_.plname, "PCMU") == 0) { res = WriteWavHeader(wav, 8000, 1, channels, kWavFormatMuLaw, _bytesWritten); } else if(STR_CASE_CMP(codec_info_.plname, "PCMA") == 0) { res = WriteWavHeader(wav, 8000, 1, channels, kWavFormatALaw, _bytesWritten); } else { // Allow calling this API even if not writing to a WAVE file. // TODO (hellner): why?! return 0; } return res; } int32_t ModuleFileUtility::InitPreEncodedReading(InStream& in, const CodecInst& cinst) { uint8_t preEncodedID; in.Read(&preEncodedID, 1); MediaFileUtility_CodecType codecType = (MediaFileUtility_CodecType)preEncodedID; if(set_codec_info(cinst) != 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Pre-encoded file send codec mismatch!"); return -1; } if(codecType != _codecId) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "Pre-encoded file format codec mismatch!"); return -1; } memcpy(&codec_info_,&cinst,sizeof(CodecInst)); _reading = true; return 0; } int32_t ModuleFileUtility::ReadPreEncodedData( InStream& in, int8_t* outData, const size_t bufferSize) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadPreEncodedData(in= 0x%x, " "outData= 0x%x, bufferSize= %" PRIuS ")", &in, outData, bufferSize); if(outData == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "output buffer NULL"); } size_t frameLen; uint8_t buf[64]; // Each frame has a two byte header containing the frame length. int32_t res = in.Read(buf, 2); if(res != 2) { if(!in.Rewind()) { // The first byte is the codec identifier. in.Read(buf, 1); res = in.Read(buf, 2); } else { return -1; } } frameLen = buf[0] + buf[1] * 256; if(bufferSize < frameLen) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "buffer not large enough to read %" PRIuS " bytes of " "pre-encoded data!", frameLen); return -1; } return in.Read(outData, frameLen); } int32_t ModuleFileUtility::InitPreEncodedWriting( OutStream& out, const CodecInst& codecInst) { if(set_codec_info(codecInst) != 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "CodecInst not recognized!"); return -1; } _writing = true; _bytesWritten = 1; out.Write(&_codecId, 1); return 0; } int32_t ModuleFileUtility::WritePreEncodedData( OutStream& out, const int8_t* buffer, const size_t dataLength) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::WritePreEncodedData(out= 0x%x, " "inData= 0x%x, dataLen= %" PRIuS ")", &out, buffer, dataLength); if(buffer == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id,"buffer NULL"); } size_t bytesWritten = 0; // The first two bytes is the size of the frame. int16_t lengthBuf; lengthBuf = (int16_t)dataLength; if(dataLength > static_cast(std::numeric_limits::max()) || !out.Write(&lengthBuf, 2)) { return -1; } bytesWritten = 2; if(!out.Write(buffer, dataLength)) { return -1; } bytesWritten += dataLength; return static_cast(bytesWritten); } int32_t ModuleFileUtility::InitCompressedReading( InStream& in, const uint32_t start, const uint32_t stop) { WEBRTC_TRACE(kTraceDebug, kTraceFile, _id, "ModuleFileUtility::InitCompressedReading(in= 0x%x, " "start= %d, stop= %d)", &in, start, stop); #if defined(WEBRTC_CODEC_ILBC) int16_t read_len = 0; #endif _codecId = kCodecNoCodec; _playoutPositionMs = 0; _reading = false; _startPointInMs = start; _stopPointInMs = stop; // Read the codec name int32_t cnt = 0; char buf[64]; do { in.Read(&buf[cnt++], 1); } while ((buf[cnt-1] != '\n') && (64 > cnt)); if(cnt==64) { return -1; } buf[cnt]=0; #ifdef WEBRTC_CODEC_ILBC if(!strcmp("#!iLBC20\n", buf)) { codec_info_.pltype = 102; strcpy(codec_info_.plname, "ilbc"); codec_info_.plfreq = 8000; codec_info_.pacsize = 160; codec_info_.channels = 1; codec_info_.rate = 13300; _codecId = kCodecIlbc20Ms; if(_startPointInMs > 0) { while (_playoutPositionMs <= _startPointInMs) { read_len = in.Read(buf, 38); if(read_len != 38) { return -1; } _playoutPositionMs += 20; } } } if(!strcmp("#!iLBC30\n", buf)) { codec_info_.pltype = 102; strcpy(codec_info_.plname, "ilbc"); codec_info_.plfreq = 8000; codec_info_.pacsize = 240; codec_info_.channels = 1; codec_info_.rate = 13300; _codecId = kCodecIlbc30Ms; if(_startPointInMs > 0) { while (_playoutPositionMs <= _startPointInMs) { read_len = in.Read(buf, 50); if(read_len != 50) { return -1; } _playoutPositionMs += 20; } } } #endif if(_codecId == kCodecNoCodec) { return -1; } _reading = true; return 0; } int32_t ModuleFileUtility::ReadCompressedData(InStream& in, int8_t* outData, size_t bufferSize) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadCompressedData(in=0x%x, outData=0x%x, " "bytes=%" PRIuS ")", &in, outData, bufferSize); int bytesRead = 0; if(! _reading) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "not currently reading!"); return -1; } #ifdef WEBRTC_CODEC_ILBC if((_codecId == kCodecIlbc20Ms) || (_codecId == kCodecIlbc30Ms)) { size_t byteSize = 0; if(_codecId == kCodecIlbc30Ms) { byteSize = 50; } if(_codecId == kCodecIlbc20Ms) { byteSize = 38; } if(bufferSize < byteSize) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "output buffer is too short to read ILBC compressed " "data."); assert(false); return -1; } bytesRead = in.Read(outData, byteSize); if(bytesRead != static_cast(byteSize)) { if(!in.Rewind()) { InitCompressedReading(in, _startPointInMs, _stopPointInMs); bytesRead = in.Read(outData, byteSize); if(bytesRead != static_cast(byteSize)) { _reading = false; return -1; } } else { _reading = false; return -1; } } } #endif if(bytesRead == 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadCompressedData() no bytes read, codec not supported"); return -1; } _playoutPositionMs += 20; if((_stopPointInMs > 0) && (_playoutPositionMs >= _stopPointInMs)) { if(!in.Rewind()) { InitCompressedReading(in, _startPointInMs, _stopPointInMs); } else { _reading = false; } } return bytesRead; } int32_t ModuleFileUtility::InitCompressedWriting( OutStream& out, const CodecInst& codecInst) { WEBRTC_TRACE(kTraceDebug, kTraceFile, _id, "ModuleFileUtility::InitCompressedWriting(out= 0x%x, " "codecName= %s)", &out, codecInst.plname); _writing = false; #ifdef WEBRTC_CODEC_ILBC if(STR_CASE_CMP(codecInst.plname, "ilbc") == 0) { if(codecInst.pacsize == 160) { _codecId = kCodecIlbc20Ms; out.Write("#!iLBC20\n",9); } else if(codecInst.pacsize == 240) { _codecId = kCodecIlbc30Ms; out.Write("#!iLBC30\n",9); } else { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "codecInst defines unsupported compression codec!"); return -1; } memcpy(&codec_info_,&codecInst,sizeof(CodecInst)); _writing = true; return 0; } #endif WEBRTC_TRACE(kTraceError, kTraceFile, _id, "codecInst defines unsupported compression codec!"); return -1; } int32_t ModuleFileUtility::WriteCompressedData( OutStream& out, const int8_t* buffer, const size_t dataLength) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::WriteCompressedData(out= 0x%x, buf= 0x%x, " "dataLen= %" PRIuS ")", &out, buffer, dataLength); if(buffer == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id,"buffer NULL"); } if(!out.Write(buffer, dataLength)) { return -1; } return static_cast(dataLength); } int32_t ModuleFileUtility::InitPCMReading(InStream& pcm, const uint32_t start, const uint32_t stop, uint32_t freq) { WEBRTC_TRACE(kTraceInfo, kTraceFile, _id, "ModuleFileUtility::InitPCMReading(pcm= 0x%x, start=%d, " "stop=%d, freq=%d)", &pcm, start, stop, freq); int8_t dummy[320]; int read_len; _playoutPositionMs = 0; _startPointInMs = start; _stopPointInMs = stop; _reading = false; if(freq == 8000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 8000; codec_info_.pacsize = 160; codec_info_.channels = 1; codec_info_.rate = 128000; _codecId = kCodecL16_8Khz; } else if(freq == 16000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 16000; codec_info_.pacsize = 320; codec_info_.channels = 1; codec_info_.rate = 256000; _codecId = kCodecL16_16kHz; } else if(freq == 32000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 32000; codec_info_.pacsize = 320; codec_info_.channels = 1; codec_info_.rate = 512000; _codecId = kCodecL16_32Khz; } // Readsize for 10ms of audio data (2 bytes per sample). _readSizeBytes = 2 * codec_info_. plfreq / 100; if(_startPointInMs > 0) { while (_playoutPositionMs < _startPointInMs) { read_len = pcm.Read(dummy, _readSizeBytes); if(read_len != static_cast(_readSizeBytes)) { return -1; // Must have reached EOF before start position! } _playoutPositionMs += 10; } } _reading = true; return 0; } int32_t ModuleFileUtility::ReadPCMData(InStream& pcm, int8_t* outData, size_t bufferSize) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::ReadPCMData(pcm= 0x%x, outData= 0x%x, " "bufSize= %" PRIuS ")", &pcm, outData, bufferSize); if(outData == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "buffer NULL"); } // Readsize for 10ms of audio data (2 bytes per sample). size_t bytesRequested = static_cast(2 * codec_info_.plfreq / 100); if(bufferSize < bytesRequested) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadPCMData: buffer not long enough for a 10ms frame."); assert(false); return -1; } int bytesRead = pcm.Read(outData, bytesRequested); if(bytesRead < static_cast(bytesRequested)) { if(pcm.Rewind() == -1) { _reading = false; } else { if(InitPCMReading(pcm, _startPointInMs, _stopPointInMs, codec_info_.plfreq) == -1) { _reading = false; } else { size_t rest = bytesRequested - bytesRead; int len = pcm.Read(&(outData[bytesRead]), rest); if(len == static_cast(rest)) { bytesRead += len; } else { _reading = false; } } if(bytesRead <= 0) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "ReadPCMData: Failed to rewind audio file."); return -1; } } } if(bytesRead <= 0) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ReadPCMData: end of file"); return -1; } _playoutPositionMs += 10; if(_stopPointInMs && _playoutPositionMs >= _stopPointInMs) { if(!pcm.Rewind()) { if(InitPCMReading(pcm, _startPointInMs, _stopPointInMs, codec_info_.plfreq) == -1) { _reading = false; } } } return bytesRead; } int32_t ModuleFileUtility::InitPCMWriting(OutStream& out, uint32_t freq) { if(freq == 8000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 8000; codec_info_.pacsize = 160; codec_info_.channels = 1; codec_info_.rate = 128000; _codecId = kCodecL16_8Khz; } else if(freq == 16000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 16000; codec_info_.pacsize = 320; codec_info_.channels = 1; codec_info_.rate = 256000; _codecId = kCodecL16_16kHz; } else if(freq == 32000) { strcpy(codec_info_.plname, "L16"); codec_info_.pltype = -1; codec_info_.plfreq = 32000; codec_info_.pacsize = 320; codec_info_.channels = 1; codec_info_.rate = 512000; _codecId = kCodecL16_32Khz; } if((_codecId != kCodecL16_8Khz) && (_codecId != kCodecL16_16kHz) && (_codecId != kCodecL16_32Khz)) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "CodecInst is not 8KHz PCM or 16KHz PCM!"); return -1; } _writing = true; _bytesWritten = 0; return 0; } int32_t ModuleFileUtility::WritePCMData(OutStream& out, const int8_t* buffer, const size_t dataLength) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::WritePCMData(out= 0x%x, buf= 0x%x, " "dataLen= %" PRIuS ")", &out, buffer, dataLength); if(buffer == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "buffer NULL"); } if(!out.Write(buffer, dataLength)) { return -1; } _bytesWritten += dataLength; return static_cast(dataLength); } int32_t ModuleFileUtility::codec_info(CodecInst& codecInst) { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::codec_info(codecInst= 0x%x)", &codecInst); if(!_reading && !_writing) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "CodecInst: not currently reading audio file!"); return -1; } memcpy(&codecInst,&codec_info_,sizeof(CodecInst)); return 0; } int32_t ModuleFileUtility::set_codec_info(const CodecInst& codecInst) { _codecId = kCodecNoCodec; if(STR_CASE_CMP(codecInst.plname, "PCMU") == 0) { _codecId = kCodecPcmu; } else if(STR_CASE_CMP(codecInst.plname, "PCMA") == 0) { _codecId = kCodecPcma; } else if(STR_CASE_CMP(codecInst.plname, "L16") == 0) { if(codecInst.plfreq == 8000) { _codecId = kCodecL16_8Khz; } else if(codecInst.plfreq == 16000) { _codecId = kCodecL16_16kHz; } else if(codecInst.plfreq == 32000) { _codecId = kCodecL16_32Khz; } } #ifdef WEBRTC_CODEC_ILBC else if(STR_CASE_CMP(codecInst.plname, "ilbc") == 0) { if(codecInst.pacsize == 160) { _codecId = kCodecIlbc20Ms; } else if(codecInst.pacsize == 240) { _codecId = kCodecIlbc30Ms; } } #endif #if(defined(WEBRTC_CODEC_ISAC) || defined(WEBRTC_CODEC_ISACFX)) else if(STR_CASE_CMP(codecInst.plname, "isac") == 0) { if(codecInst.plfreq == 16000) { _codecId = kCodecIsac; } else if(codecInst.plfreq == 32000) { _codecId = kCodecIsacSwb; } } #endif #ifdef WEBRTC_CODEC_G722 else if(STR_CASE_CMP(codecInst.plname, "G722") == 0) { _codecId = kCodecG722; } #endif if(_codecId == kCodecNoCodec) { return -1; } memcpy(&codec_info_, &codecInst, sizeof(CodecInst)); return 0; } int32_t ModuleFileUtility::FileDurationMs(const char* fileName, const FileFormats fileFormat, const uint32_t freqInHz) { if(fileName == NULL) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "filename NULL"); return -1; } int32_t time_in_ms = -1; struct stat file_size; if(stat(fileName,&file_size) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "failed to retrieve file size with stat!"); return -1; } FileWrapper* inStreamObj = FileWrapper::Create(); if(inStreamObj == NULL) { WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "failed to create InStream object!"); return -1; } if (!inStreamObj->OpenFile(fileName, true)) { delete inStreamObj; WEBRTC_TRACE(kTraceError, kTraceFile, _id, "failed to open file %s!", fileName); return -1; } switch (fileFormat) { case kFileFormatWavFile: { if(ReadWavHeader(*inStreamObj) == -1) { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "failed to read WAV file header!"); return -1; } time_in_ms = ((file_size.st_size - 44) / (_wavFormatObj.nAvgBytesPerSec/1000)); break; } case kFileFormatPcm16kHzFile: { // 16 samples per ms. 2 bytes per sample. int32_t denominator = 16*2; time_in_ms = (file_size.st_size)/denominator; break; } case kFileFormatPcm8kHzFile: { // 8 samples per ms. 2 bytes per sample. int32_t denominator = 8*2; time_in_ms = (file_size.st_size)/denominator; break; } case kFileFormatCompressedFile: { int32_t cnt = 0; int read_len = 0; char buf[64]; do { read_len = inStreamObj->Read(&buf[cnt++], 1); if(read_len != 1) { return -1; } } while ((buf[cnt-1] != '\n') && (64 > cnt)); if(cnt == 64) { return -1; } else { buf[cnt] = 0; } #ifdef WEBRTC_CODEC_ILBC if(!strcmp("#!iLBC20\n", buf)) { // 20 ms is 304 bits time_in_ms = ((file_size.st_size)*160)/304; break; } if(!strcmp("#!iLBC30\n", buf)) { // 30 ms takes 400 bits. // file size in bytes * 8 / 400 is the number of // 30 ms frames in the file -> // time_in_ms = file size * 8 / 400 * 30 time_in_ms = ((file_size.st_size)*240)/400; break; } #endif break; } case kFileFormatPreencodedFile: { WEBRTC_TRACE(kTraceError, kTraceFile, _id, "cannot determine duration of Pre-Encoded file!"); break; } default: WEBRTC_TRACE(kTraceError, kTraceFile, _id, "unsupported file format %d!", fileFormat); break; } inStreamObj->CloseFile(); delete inStreamObj; return time_in_ms; } uint32_t ModuleFileUtility::PlayoutPositionMs() { WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "ModuleFileUtility::PlayoutPosition()"); return _reading ? _playoutPositionMs : 0; } } // namespace webrtc