/*
 *  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 <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <limits>

#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/typedefs.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<int>(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<int>(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<size_t>(_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<int>(_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<size_t>(_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<int32_t>(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<int16_t*>(_tempData);
        int16_t* outLeft = reinterpret_cast<int16_t*>(outDataLeft);
        int16_t* outRight = reinterpret_cast<int16_t*>(
            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<int32_t>(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<int32_t>(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<WavFormat>(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<size_t>(std::numeric_limits<int16_t>::max()) ||
       !out.Write(&lengthBuf, 2))
    {
       return -1;
    }
    bytesWritten = 2;

    if(!out.Write(buffer, dataLength))
    {
        return -1;
    }
    bytesWritten += dataLength;
    return static_cast<int32_t>(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<int>(byteSize))
        {
            if(!in.Rewind())
            {
                InitCompressedReading(in, _startPointInMs, _stopPointInMs);
                bytesRead = in.Read(outData, byteSize);
                if(bytesRead != static_cast<int>(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<int32_t>(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<int>(_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<size_t>(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<int>(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<int>(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<int32_t>(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
