/*
 *  Copyright (c) 2011 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 <assert.h>

#include "critical_section_wrapper.h"
#include "file_wrapper.h"
#include "media_file_impl.h"
#include "tick_util.h"
#include "trace.h"

#if (defined(WIN32) || defined(WINCE))
    #define STR_CASE_CMP _stricmp
    #define STR_NCASE_CMP _strnicmp
#else
    #define STR_CASE_CMP strcasecmp
    #define STR_NCASE_CMP strncasecmp
#endif

namespace webrtc {
MediaFile* MediaFile::CreateMediaFile(const WebRtc_Word32 id)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, id, "CreateMediaFile()");
    return new MediaFileImpl(id);
}

void MediaFile::DestroyMediaFile(MediaFile* module)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, -1, "DestroyMediaFile()");
    delete static_cast<MediaFileImpl*>(module);
}

MediaFileImpl::MediaFileImpl(const WebRtc_Word32 id)
    : _id(id),
      _crit(*CriticalSectionWrapper::CreateCriticalSection()),
      _callbackCrit(*CriticalSectionWrapper::CreateCriticalSection()),
      _ptrFileUtilityObj(NULL),
      codec_info_(),
      _ptrInStream(NULL),
      _ptrOutStream(NULL),
      _fileFormat((FileFormats)-1),
      _recordDurationMs(0),
      _playoutPositionMs(0),
      _notificationMs(0),
      _playingActive(false),
      _recordingActive(false),
      _isStereo(false),
      _openFile(false),
      _fileName(),
      _ptrCallback(NULL)
{
    WEBRTC_TRACE(kTraceMemory, kTraceFile, id, "Created");

    codec_info_.plname[0] = '\0';
    _fileName[0] = '\0';
}


MediaFileImpl::~MediaFileImpl()
{
    WEBRTC_TRACE(kTraceMemory, kTraceFile, _id, "~MediaFileImpl()");
    {
        CriticalSectionScoped lock(_crit);

        if(_playingActive)
        {
            StopPlaying();
        }

        if(_recordingActive)
        {
            StopRecording();
        }

        delete _ptrFileUtilityObj;

        if(_openFile)
        {
            delete _ptrInStream;
            _ptrInStream = NULL;
            delete _ptrOutStream;
            _ptrOutStream = NULL;
        }
    }

    delete &_crit;
    delete &_callbackCrit;
}

WebRtc_Word32 MediaFileImpl::Version(WebRtc_Word8* version,
                                     WebRtc_UWord32& remainingBufferInBytes,
                                     WebRtc_UWord32& position) const
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
                 "Version(?,bufferLength:%d,position:%d)",
                 remainingBufferInBytes, position);
    return GetVersion(version, remainingBufferInBytes, position);
}

WebRtc_Word32 MediaFile::GetVersion(WebRtc_Word8* version,
                                    WebRtc_UWord32& remainingBufferInBytes,
                                    WebRtc_UWord32& position)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, -1,
                 "GetVersion(?,bufferLength:%d,position:%d)",
                 remainingBufferInBytes, position);
    if(version == NULL)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceFile, -1,
                     "Invalid buffer pointer in argument to Version()");
        return -1;
    }
    const WebRtc_Word8 ourVersion[] = "MediaFile 1.0.0";
    const WebRtc_UWord32 ourLength =(WebRtc_UWord32)strlen(ourVersion);
    if(remainingBufferInBytes <(ourLength + 1))
    {
        return -1;
    }
    memcpy(&version[position], ourVersion, ourLength);
    version[position + ourLength] = '\0';
    remainingBufferInBytes -=(ourLength + 1);
    position +=(ourLength + 1);
    return 0;
}

WebRtc_Word32 MediaFileImpl::ChangeUniqueId(const WebRtc_Word32 id)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id, "ChangeUniqueId(new id:%d)",
                 id);
    _id = id;
    return 0;
}

WebRtc_Word32 MediaFileImpl::TimeUntilNextProcess()
{
    WEBRTC_TRACE(
        kTraceWarning,
        kTraceFile,
        _id,
        "TimeUntilNextProcess: This method is not used by MediaFile class.");
    return -1;
}

WebRtc_Word32 MediaFileImpl::Process()
{
    WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                 "Process: This method is not used by MediaFile class.");
    return -1;
}

WebRtc_Word32 MediaFileImpl::PlayoutAVIVideoData(
    WebRtc_Word8* buffer,
    WebRtc_UWord32& dataLengthInBytes)
{
    return PlayoutData( buffer, dataLengthInBytes, true);
}

WebRtc_Word32 MediaFileImpl::PlayoutAudioData(WebRtc_Word8* buffer,
                                WebRtc_UWord32& dataLengthInBytes)
{
    return PlayoutData( buffer, dataLengthInBytes, false);
}

WebRtc_Word32 MediaFileImpl::PlayoutData(WebRtc_Word8* buffer,
                                         WebRtc_UWord32& dataLengthInBytes,
                                         bool video)
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id,
               "MediaFileImpl::PlayoutData(buffer= 0x%x, bufLen= %ld)",
                 buffer, dataLengthInBytes);

    const WebRtc_UWord32 bufferLengthInBytes = dataLengthInBytes;
    dataLengthInBytes = 0;

    if(buffer == NULL || bufferLengthInBytes == 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "Buffer pointer or length is NULL!");
        return -1;
    }

    WebRtc_Word32 bytesRead = 0;
    {
        CriticalSectionScoped lock(_crit);

        if(!_playingActive)
        {
            WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                         "Not currently playing!");
            return -1;
        }

        if(!_ptrFileUtilityObj)
        {
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Playing, but no FileUtility object!");
            StopPlaying();
            return -1;
        }

        switch(_fileFormat)
        {
            case kFileFormatPcm32kHzFile:
            case kFileFormatPcm16kHzFile:
            case kFileFormatPcm8kHzFile:
                bytesRead = _ptrFileUtilityObj->ReadPCMData(
                    *_ptrInStream,
                    buffer,
                    bufferLengthInBytes);
                break;
            case kFileFormatCompressedFile:
                bytesRead = _ptrFileUtilityObj->ReadCompressedData(
                    *_ptrInStream,
                    buffer,
                    bufferLengthInBytes);
                break;
            case kFileFormatWavFile:
                bytesRead = _ptrFileUtilityObj->ReadWavDataAsMono(
                    *_ptrInStream,
                    buffer,
                    bufferLengthInBytes);
                break;
            case kFileFormatPreencodedFile:
                bytesRead = _ptrFileUtilityObj->ReadPreEncodedData(
                    *_ptrInStream,
                    buffer,
                    bufferLengthInBytes);
                if(bytesRead > 0)
                {
                    dataLengthInBytes = bytesRead;
                    return 0;
                }
                break;
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
            case kFileFormatAviFile:
            {
                if(video)
                {
                    bytesRead = _ptrFileUtilityObj->ReadAviVideoData(
                        buffer,
                        bufferLengthInBytes);
                }
                else
                {
                    bytesRead = _ptrFileUtilityObj->ReadAviAudioData(
                        buffer,
                        bufferLengthInBytes);
                }
                break;
            }
#endif
            default:
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Playing file, but file format invalid!");
                assert(false);
                break;
        }

        if( bytesRead > 0)
        {
            dataLengthInBytes =(WebRtc_UWord32) bytesRead;
        }
    }
    HandlePlayCallbacks(bytesRead);
    return 0;
}

void MediaFileImpl::HandlePlayCallbacks(WebRtc_Word32 bytesRead)
{
    bool playEnded = false;
    WebRtc_UWord32 callbackNotifyMs = 0;

    if(bytesRead > 0)
    {
        // Check if it's time for PlayNotification(..).
        _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs();
        if(_notificationMs)
        {
            if(_playoutPositionMs >= _notificationMs)
            {
                _notificationMs = 0;
                callbackNotifyMs = _playoutPositionMs;
            }
        }
    }
    else
    {
        // If no bytes were read assume end of file.
        StopPlaying();
        playEnded = true;
    }

    // Only _callbackCrit may and should be taken when making callbacks.
    CriticalSectionScoped lock(_callbackCrit);
    if(_ptrCallback)
    {
        if(callbackNotifyMs)
        {
            _ptrCallback->PlayNotification(_id, callbackNotifyMs);
        }
        if(playEnded)
        {
            _ptrCallback->PlayFileEnded(_id);
        }
    }
}

WebRtc_Word32 MediaFileImpl::PlayoutStereoData(
    WebRtc_Word8* bufferLeft,
    WebRtc_Word8* bufferRight,
    WebRtc_UWord32& dataLengthInBytes)
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id,
                 "MediaFileImpl::PlayoutStereoData(Left = 0x%x, Right = 0x%x,\
 Len= %ld)",
                 bufferLeft,
                 bufferRight,
                 dataLengthInBytes);

    const WebRtc_UWord32 bufferLengthInBytes = dataLengthInBytes;
    dataLengthInBytes = 0;

    if(bufferLeft == NULL || bufferRight == NULL || bufferLengthInBytes == 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "A buffer pointer or the length is NULL!");
        return -1;
    }

    bool playEnded = false;
    WebRtc_UWord32 callbackNotifyMs = 0;
    {
        CriticalSectionScoped lock(_crit);

        if(!_playingActive || !_isStereo)
        {
            WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                         "Not currently playing stereo!");
            return -1;
        }

        if(!_ptrFileUtilityObj)
        {
            WEBRTC_TRACE(
                kTraceError,
                kTraceFile,
                _id,
                "Playing stereo, but the FileUtility objects is NULL!");
            StopPlaying();
            return -1;
        }

        // Stereo playout only supported for WAV files.
        WebRtc_Word32 bytesRead = 0;
        switch(_fileFormat)
        {
            case kFileFormatWavFile:
                    bytesRead = _ptrFileUtilityObj->ReadWavDataAsStereo(
                        *_ptrInStream,
                        bufferLeft,
                        bufferRight,
                        bufferLengthInBytes);
                    break;
            default:
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Trying to read non-WAV as stereo audio\
 (not supported)");
                break;
        }

        if(bytesRead > 0)
        {
            dataLengthInBytes = bytesRead;

            // Check if it's time for PlayNotification(..).
            _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs();
            if(_notificationMs)
            {
                if(_playoutPositionMs >= _notificationMs)
                {
                    _notificationMs = 0;
                    callbackNotifyMs = _playoutPositionMs;
                }
            }
        }
        else
        {
            // If no bytes were read assume end of file.
            StopPlaying();
            playEnded = true;
        }
    }

    CriticalSectionScoped lock(_callbackCrit);
    if(_ptrCallback)
    {
        if(callbackNotifyMs)
        {
            _ptrCallback->PlayNotification(_id, callbackNotifyMs);
        }
        if(playEnded)
        {
            _ptrCallback->PlayFileEnded(_id);
        }
    }
    return 0;
}

WebRtc_Word32 MediaFileImpl::StartPlayingAudioFile(
    const WebRtc_Word8* fileName,
    const WebRtc_UWord32 notificationTimeMs,
    const bool loop,
    const FileFormats format,
    const CodecInst* codecInst,
    const WebRtc_UWord32 startPointMs,
    const WebRtc_UWord32 stopPointMs)
{
    const bool videoOnly = false;
    return StartPlayingFile(fileName, notificationTimeMs, loop, videoOnly,
                            format, codecInst, startPointMs, stopPointMs);
}


WebRtc_Word32 MediaFileImpl::StartPlayingVideoFile(const WebRtc_Word8* fileName,
                                                   const bool loop,
                                                   bool videoOnly,
                                                   const FileFormats format)
{

    const WebRtc_UWord32 notificationTimeMs = 0;
    const WebRtc_UWord32 startPointMs       = 0;
    const WebRtc_UWord32 stopPointMs        = 0;
    return StartPlayingFile(fileName, notificationTimeMs, loop, videoOnly,
                            format, 0, startPointMs, stopPointMs);
}

WebRtc_Word32 MediaFileImpl::StartPlayingFile(
    const WebRtc_Word8* fileName,
    const WebRtc_UWord32 notificationTimeMs,
    const bool loop,
    bool videoOnly,
    const FileFormats format,
    const CodecInst* codecInst,
    const WebRtc_UWord32 startPointMs,
    const WebRtc_UWord32 stopPointMs)
{
    WEBRTC_TRACE(
        kTraceModuleCall,
        kTraceFile,
        _id,
        "MediaFileImpl::StartPlayingFile: fileName= %s, notify= %d, loop= %d,\
 format= %d, codecInst=%s, start= %d, stop= %d",
        (fileName == NULL) ? "NULL" : fileName,
        notificationTimeMs,
        loop,
        format,(codecInst == NULL) ? "NULL" : codecInst->plname,
        startPointMs,
        stopPointMs);

    if(!ValidFileName(fileName))
    {
        return -1;
    }
    if(!ValidFileFormat(format,codecInst))
    {
        return -1;
    }
    if(!ValidFilePositions(startPointMs,stopPointMs))
    {
        return -1;
    }

    // Check that the file will play longer than notificationTimeMs ms.
    if((startPointMs && stopPointMs && !loop) &&
       (notificationTimeMs > (stopPointMs - startPointMs)))
    {
        WEBRTC_TRACE(
            kTraceError,
            kTraceFile,
            _id,
            "specified notification time is longer than amount of ms that will\
 be played");
        return -1;
    }

    FileWrapper* inputStream = FileWrapper::Create();
    if(inputStream == NULL)
    {
       WEBRTC_TRACE(kTraceMemory, kTraceFile, _id,
                    "Failed to allocate input stream for file %s", fileName);
        return -1;
    }

    // TODO (hellner): make all formats support reading from stream.
    bool useStream = (format != kFileFormatAviFile);
    if( useStream)
    {
        if(inputStream->OpenFile(fileName, true, loop) != 0)
        {
            delete inputStream;
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Could not open input file %s", fileName);
            return -1;
        }
    }

    if(StartPlayingStream(*inputStream, fileName, loop, notificationTimeMs,
                          format, codecInst, startPointMs, stopPointMs,
                          videoOnly) == -1)
    {
        if( useStream)
        {
            inputStream->CloseFile();
        }
        delete inputStream;
        return -1;
    }

    CriticalSectionScoped lock(_crit);
    _openFile = true;
    strncpy(_fileName, fileName, sizeof(_fileName));
    _fileName[sizeof(_fileName) - 1] = '\0';
    return 0;
}

WebRtc_Word32 MediaFileImpl::StartPlayingAudioStream(
    InStream& stream,
    const WebRtc_UWord32 notificationTimeMs,
    const FileFormats format,
    const CodecInst* codecInst,
    const WebRtc_UWord32 startPointMs,
    const WebRtc_UWord32 stopPointMs)
{
    return StartPlayingStream(stream, 0, false, notificationTimeMs, format,
                              codecInst, startPointMs, stopPointMs);
}

WebRtc_Word32 MediaFileImpl::StartPlayingStream(
    InStream& stream,
    const WebRtc_Word8* filename,
    bool loop,
    const WebRtc_UWord32 notificationTimeMs,
    const FileFormats format,
    const CodecInst*  codecInst,
    const WebRtc_UWord32 startPointMs,
    const WebRtc_UWord32 stopPointMs,
    bool videoOnly)
{
    WEBRTC_TRACE(
        kTraceModuleCall,
        kTraceFile,
        _id,
        "MediaFileImpl::StartPlayingStream(stream=0x%x, notify=%ld, format=%d,\
 codecInst=%s, start=%ld, stop=%ld",
        &stream,
        notificationTimeMs,
        format,
        (codecInst == NULL) ? "NULL" : codecInst->plname,
        startPointMs,
        stopPointMs);

    if(!ValidFileFormat(format,codecInst))
    {
        return -1;
    }

    if(!ValidFilePositions(startPointMs,stopPointMs))
    {
        return -1;
    }

    CriticalSectionScoped lock(_crit);
    if(_playingActive || _recordingActive)
    {
        WEBRTC_TRACE(
            kTraceError,
            kTraceFile,
            _id,
            "StartPlaying called, but already playing or recording file %s",
            (_fileName == NULL) ? "NULL" : _fileName);
        return -1;
    }

    if(_ptrFileUtilityObj != NULL)
    {
        WEBRTC_TRACE(kTraceError,
                     kTraceFile,
                     _id,
                     "StartPlaying called, but FileUtilityObj already exists!");
        StopPlaying();
        return -1;
    }

    _ptrFileUtilityObj = new ModuleFileUtility(_id);
    if(_ptrFileUtilityObj == NULL)
    {
        WEBRTC_TRACE(kTraceMemory, kTraceFile, _id,
                     "Failed to create FileUtilityObj!");
        return -1;
    }

    switch(format)
    {
        case kFileFormatWavFile:
        {
            if(_ptrFileUtilityObj->InitWavReading(stream, startPointMs,
                                                  stopPointMs) == -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Not a valid WAV file!");
                StopPlaying();
                return -1;
            }
            _fileFormat = kFileFormatWavFile;
            break;
        }
        case kFileFormatCompressedFile:
        {
            if(_ptrFileUtilityObj->InitCompressedReading(stream, startPointMs,
                                                         stopPointMs) == -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Not a valid Compressed file!");
                StopPlaying();
                return -1;
            }
            _fileFormat = kFileFormatCompressedFile;
            break;
        }
        case kFileFormatPcm8kHzFile:
        case kFileFormatPcm16kHzFile:
        case kFileFormatPcm32kHzFile:
        {
            if(!ValidFrequency(codecInst->plfreq) ||
               _ptrFileUtilityObj->InitPCMReading(stream, startPointMs,
                                                  stopPointMs,
                                                  codecInst->plfreq) == -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Not a valid raw 8 or 16 KHz PCM file!");
                StopPlaying();
                return -1;
            }

            _fileFormat = format;
            break;
        }
        case kFileFormatPreencodedFile:
        {
            if(_ptrFileUtilityObj->InitPreEncodedReading(stream, *codecInst) ==
               -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Not a valid PreEncoded file!");
                StopPlaying();
                return -1;
            }

            _fileFormat = kFileFormatPreencodedFile;
            break;
        }
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
        case kFileFormatAviFile:
        {
            if(_ptrFileUtilityObj->InitAviReading( filename, videoOnly, loop))
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Not a valid AVI file!");
                StopPlaying();

                return -1;
            }

            _ptrFileUtilityObj->codec_info(codec_info_);

            _fileFormat = kFileFormatAviFile;
            break;
        }
#endif
        default:
        {
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Invalid file format specified!");
            StopPlaying();
            return -1;
        }
    }
    if(_ptrFileUtilityObj->codec_info(codec_info_) == -1)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "Failed to retrieve codec info!");
        StopPlaying();
        return -1;
    }

    _isStereo = (codec_info_.channels == 2);
    if(_isStereo && (_fileFormat != kFileFormatWavFile))
    {
        WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                     "Stereo is only allowed for WAV files");
        StopPlaying();
        return -1;
    }
    _playingActive = true;
    _playoutPositionMs = _ptrFileUtilityObj->PlayoutPositionMs();
    _ptrInStream = &stream;
    _notificationMs = notificationTimeMs;

    return 0;
}

WebRtc_Word32 MediaFileImpl::StopPlaying()
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
                 "MediaFileImpl::StopPlaying()");

    CriticalSectionScoped lock(_crit);
    _isStereo = false;
    if(_ptrFileUtilityObj)
    {
        delete _ptrFileUtilityObj;
        _ptrFileUtilityObj = NULL;
    }
    if(_ptrInStream)
    {
        // If MediaFileImpl opened the InStream it must be reclaimed here.
        if(_openFile)
        {
            delete _ptrInStream;
            _openFile = false;
        }
        _ptrInStream = NULL;
    }

    codec_info_.pltype = 0;
    codec_info_.plname[0] = '\0';

    if(!_playingActive)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                     "playing is not active!");
        return -1;
    }

    _playingActive = false;
    return 0;
}

bool MediaFileImpl::IsPlaying()
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsPlaying()");
    CriticalSectionScoped lock(_crit);
    return _playingActive;
}

WebRtc_Word32 MediaFileImpl::IncomingAudioData(
    const WebRtc_Word8*  buffer,
    const WebRtc_UWord32 bufferLengthInBytes)
{
    return IncomingAudioVideoData( buffer, bufferLengthInBytes, false);
}

WebRtc_Word32 MediaFileImpl::IncomingAVIVideoData(
    const WebRtc_Word8*  buffer,
    const WebRtc_UWord32 bufferLengthInBytes)
{
    return IncomingAudioVideoData( buffer, bufferLengthInBytes, true);
}

WebRtc_Word32 MediaFileImpl::IncomingAudioVideoData(
    const WebRtc_Word8*  buffer,
    const WebRtc_UWord32 bufferLengthInBytes,
    const bool video)
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id,
                 "MediaFile::IncomingData(buffer= 0x%x, bufLen= %hd",
                 buffer, bufferLengthInBytes);

    if(buffer == NULL || bufferLengthInBytes == 0)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "Buffer pointer or length is NULL!");
        return -1;
    }

    bool recordingEnded = false;
    WebRtc_UWord32 callbackNotifyMs = 0;
    {
        CriticalSectionScoped lock(_crit);

        if(!_recordingActive)
        {
            WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                         "Not currently recording!");
            return -1;
        }
        if(_ptrOutStream == NULL)
        {
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Recording is active, but output stream is NULL!");
            assert(false);
            return -1;
        }

        WebRtc_Word32 bytesWritten = 0;
        WebRtc_UWord32 samplesWritten = codec_info_.pacsize;
        if(_ptrFileUtilityObj)
        {
            switch(_fileFormat)
            {
                case kFileFormatPcm8kHzFile:
                case kFileFormatPcm16kHzFile:
                case kFileFormatPcm32kHzFile:
                    bytesWritten = _ptrFileUtilityObj->WritePCMData(
                        *_ptrOutStream,
                        buffer,
                        bufferLengthInBytes);

                    // Sample size is 2 bytes.
                    if(bytesWritten > 0)
                    {
                        samplesWritten = bytesWritten/sizeof(WebRtc_Word16);
                    }
                    break;
                case kFileFormatCompressedFile:
                    bytesWritten = _ptrFileUtilityObj->WriteCompressedData(
                        *_ptrOutStream, buffer, bufferLengthInBytes);
                    break;
                case kFileFormatWavFile:
                    bytesWritten = _ptrFileUtilityObj->WriteWavData(
                        *_ptrOutStream,
                        buffer,
                        bufferLengthInBytes);
                    if(bytesWritten > 0 && STR_NCASE_CMP(codec_info_.plname,
                                                         "L16", 4) == 0)
                    {
                        // Sample size is 2 bytes.
                        samplesWritten = bytesWritten/sizeof(WebRtc_Word16);
                    }
                    break;
                case kFileFormatPreencodedFile:
                    bytesWritten = _ptrFileUtilityObj->WritePreEncodedData(
                        *_ptrOutStream, buffer, bufferLengthInBytes);
                    break;
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
                case kFileFormatAviFile:
                    if(video)
                    {
                        bytesWritten = _ptrFileUtilityObj->WriteAviVideoData(
                            buffer, bufferLengthInBytes);
                    }else
                    {
                        bytesWritten = _ptrFileUtilityObj->WriteAviAudioData(
                            buffer, bufferLengthInBytes);
                    }
                    break;
#endif
                default:
                    WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                                 "recording active, but file format invalid!");
                    break;
            }
        } else {
            // TODO (hellner): quick look at the code makes me think that this
            //                 code is never executed. Remove?
            if(_ptrOutStream)
            {
                if(_ptrOutStream->Write(buffer, bufferLengthInBytes))
                {
                    bytesWritten = bufferLengthInBytes;
                }
            }
        }

        if(!video)
        {
            _recordDurationMs += samplesWritten / (codec_info_.plfreq / 1000);
        }

        // Check if it's time for RecordNotification(..).
        if(_notificationMs)
        {
            if(_recordDurationMs  >= _notificationMs)
            {
                _notificationMs = 0;
                callbackNotifyMs = _recordDurationMs;
            }
        }
        if(bytesWritten < (WebRtc_Word32)bufferLengthInBytes)
        {
            WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                         "Failed to write all requested bytes!");
            StopRecording();
            recordingEnded = true;
        }
    }

    // Only _callbackCrit may and should be taken when making callbacks.
    CriticalSectionScoped lock(_callbackCrit);
    if(_ptrCallback)
    {
        if(callbackNotifyMs)
        {
            _ptrCallback->RecordNotification(_id, callbackNotifyMs);
        }
        if(recordingEnded)
        {
            _ptrCallback->RecordFileEnded(_id);
            return -1;
        }
    }
    return 0;
}

WebRtc_Word32 MediaFileImpl::StartRecordingAudioFile(
    const WebRtc_Word8* fileName,
    const FileFormats format,
    const CodecInst& codecInst,
    const WebRtc_UWord32 notificationTimeMs,
    const WebRtc_UWord32 maxSizeBytes)
{
    VideoCodec dummyCodecInst;
    return StartRecordingFile(fileName, format, codecInst, dummyCodecInst,
                              notificationTimeMs, maxSizeBytes);
}


WebRtc_Word32 MediaFileImpl::StartRecordingVideoFile(
    const WebRtc_Word8* fileName,
    const FileFormats format,
    const CodecInst& codecInst,
    const VideoCodec& videoCodecInst,
    bool videoOnly)
{
    const WebRtc_UWord32 notificationTimeMs = 0;
    const WebRtc_UWord32 maxSizeBytes       = 0;

    return StartRecordingFile(fileName, format, codecInst, videoCodecInst,
                              notificationTimeMs, maxSizeBytes, videoOnly);
}

WebRtc_Word32 MediaFileImpl::StartRecordingFile(
    const WebRtc_Word8* fileName,
    const FileFormats format,
    const CodecInst& codecInst,
    const VideoCodec& videoCodecInst,
    const WebRtc_UWord32 notificationTimeMs,
    const WebRtc_UWord32 maxSizeBytes,
    bool videoOnly)
{
    WEBRTC_TRACE(kTraceModuleCall,
                 kTraceFile,
                 _id,
                 "MediaFileImpl::StartRecordingFile(fileName= %s, format= %d,\
 codecInst= %s, notificationMs= %d, maxSize= %d",
                 (fileName == NULL) ? "NULL" : fileName,
                 format,
                 (codecInst.plname[0] == '\0') ? "NULL" : codecInst.plname,
                 notificationTimeMs,
                 maxSizeBytes);

    if(!ValidFileName(fileName))
    {
        return -1;
    }
    if(!ValidFileFormat(format,&codecInst))
    {
        return -1;
    }

    FileWrapper* outputStream = FileWrapper::Create();
    if(outputStream == NULL)
    {
        WEBRTC_TRACE(kTraceMemory, kTraceFile, _id,
                     "Failed to allocate memory for output stream");
        return -1;
    }

    // TODO (hellner): make all formats support writing to stream.
    const bool useStream = ( format != kFileFormatAviFile);
    if( useStream)
    {
        if(outputStream->OpenFile(fileName, false) != 0)
        {
            delete outputStream;
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Could not open output file '%s' for writing!",
                         fileName);
            return -1;
        }
    }
    if(maxSizeBytes)
    {
        outputStream->SetMaxFileSize(maxSizeBytes);
    }

    if(StartRecordingStream(*outputStream, fileName, format, codecInst,
                            videoCodecInst, notificationTimeMs,
                            videoOnly) == -1)
    {
        if( useStream)
        {
            outputStream->CloseFile();
        }
        delete outputStream;
        return -1;
    }

    CriticalSectionScoped lock(_crit);
    _openFile = true;
    strncpy(_fileName, fileName, sizeof(_fileName));
    _fileName[sizeof(_fileName) - 1] = '\0';
    return 0;
}

WebRtc_Word32 MediaFileImpl::StartRecordingAudioStream(
    OutStream& stream,
    const FileFormats format,
    const CodecInst& codecInst,
    const WebRtc_UWord32 notificationTimeMs)
{
    VideoCodec dummyCodecInst;
    return StartRecordingStream(stream, 0, format, codecInst, dummyCodecInst,
                                notificationTimeMs);
}

WebRtc_Word32 MediaFileImpl::StartRecordingStream(
    OutStream& stream,
    const WebRtc_Word8* fileName,
    const FileFormats format,
    const CodecInst& codecInst,
    const VideoCodec& videoCodecInst,
    const WebRtc_UWord32 notificationTimeMs,
    bool videoOnly)
{
    WEBRTC_TRACE(
        kTraceModuleCall,
        kTraceFile,
        _id,
        "MediaFileImpl::StartRecordingStream: format= %d, codec= %s,\
 notify= %d",
        format,(codecInst.plname[0] == '\0') ? "NULL" : codecInst.plname,
        notificationTimeMs);

    // Check codec info
    if(!ValidFileFormat(format,&codecInst))
    {
        return -1;
    }

    CriticalSectionScoped lock(_crit);
    if(_recordingActive || _playingActive)
    {
        WEBRTC_TRACE(
            kTraceError,
            kTraceFile,
            _id,
            "StartRecording called, but already recording or playing file %s!",
                   _fileName);
        return -1;
    }

    if(_ptrFileUtilityObj != NULL)
    {
        WEBRTC_TRACE(
            kTraceError,
            kTraceFile,
            _id,
            "StartRecording called, but fileUtilityObj already exists!");
        StopRecording();
        return -1;
    }

    _ptrFileUtilityObj = new ModuleFileUtility(_id);
    if(_ptrFileUtilityObj == NULL)
    {
        WEBRTC_TRACE(kTraceMemory, kTraceFile, _id,
                     "Cannot allocate fileUtilityObj!");
        return -1;
    }

    CodecInst tmpAudioCodec;
    memcpy(&tmpAudioCodec, &codecInst, sizeof(CodecInst));
    switch(format)
    {
        case kFileFormatWavFile:
        {
            if(_ptrFileUtilityObj->InitWavWriting(stream, codecInst) == -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Failed to initialize WAV file!");
                delete _ptrFileUtilityObj;
                _ptrFileUtilityObj = NULL;
                return -1;
            }
            _fileFormat = kFileFormatWavFile;
            break;
        }
        case kFileFormatCompressedFile:
        {
            // Write compression codec name at beginning of file
            if(_ptrFileUtilityObj->InitCompressedWriting(stream, codecInst) ==
               -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Failed to initialize Compressed file!");
                delete _ptrFileUtilityObj;
                _ptrFileUtilityObj = NULL;
                return -1;
            }
            _fileFormat = kFileFormatCompressedFile;
            break;
        }
        case kFileFormatPcm8kHzFile:
        case kFileFormatPcm16kHzFile:
        {
            if(!ValidFrequency(codecInst.plfreq) ||
               _ptrFileUtilityObj->InitPCMWriting(stream, codecInst.plfreq) ==
               -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Failed to initialize 8 or 16KHz PCM file!");
                delete _ptrFileUtilityObj;
                _ptrFileUtilityObj = NULL;
                return -1;
            }
            _fileFormat = format;
            break;
        }
        case kFileFormatPreencodedFile:
        {
            if(_ptrFileUtilityObj->InitPreEncodedWriting(stream, codecInst) ==
               -1)
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Failed to initialize Pre-Encoded file!");
                delete _ptrFileUtilityObj;
                _ptrFileUtilityObj = NULL;
                return -1;
            }

            _fileFormat = kFileFormatPreencodedFile;
            break;
        }
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
        case kFileFormatAviFile:
        {
            if( (_ptrFileUtilityObj->InitAviWriting(
                    fileName,
                    codecInst,
                    videoCodecInst,videoOnly) == -1) ||
                    (_ptrFileUtilityObj->codec_info(tmpAudioCodec) != 0))
            {
                WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                             "Failed to initialize AVI file!");
                delete _ptrFileUtilityObj;
                _ptrFileUtilityObj = NULL;
                return -1;
            }
            _fileFormat = kFileFormatAviFile;
            break;
        }
#endif
        default:
        {
            WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                         "Invalid file format %d specified!", format);
            delete _ptrFileUtilityObj;
            _ptrFileUtilityObj = NULL;
            return -1;
        }
    }
    _isStereo = (tmpAudioCodec.channels == 2);
    if(_isStereo)
    {
        if(_fileFormat != kFileFormatWavFile)
        {
            WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                         "Stereo is only allowed for WAV files");
            StopRecording();
            return -1;
        }
        if((STR_NCASE_CMP(codec_info_.plname, "L16", 4) != 0) &&
           (STR_NCASE_CMP(codec_info_.plname, "PCMU", 5) != 0) &&
           (STR_NCASE_CMP(codec_info_.plname, "PCMA", 5) != 0))
        {
            WEBRTC_TRACE(
                kTraceWarning,
                kTraceFile,
                _id,
                "Stereo is only allowed for codec PCMU, PCMA and L16 ");
            StopRecording();
            return -1;
        }
    }
    memcpy(&codec_info_, &tmpAudioCodec, sizeof(CodecInst));
    _recordingActive = true;
    _ptrOutStream = &stream;
    _notificationMs = notificationTimeMs;
    _recordDurationMs = 0;
    return 0;
}

WebRtc_Word32 MediaFileImpl::StopRecording()
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
                 "MediaFileImpl::StopRecording()");

    CriticalSectionScoped lock(_crit);
    if(!_recordingActive)
    {
        WEBRTC_TRACE(kTraceWarning, kTraceFile, _id,
                     "recording is not active!");
        return -1;
    }

    _isStereo = false;

    if(_ptrFileUtilityObj != NULL)
    {
        // Both AVI and WAV header has to be updated before closing the stream
        // because they contain size information.
        if((_fileFormat == kFileFormatWavFile) &&
            (_ptrOutStream != NULL))
        {
            _ptrFileUtilityObj->UpdateWavHeader(*_ptrOutStream);
        }
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
        else if( _fileFormat == kFileFormatAviFile)
        {
            _ptrFileUtilityObj->CloseAviFile( );
        }
#endif
        delete _ptrFileUtilityObj;
        _ptrFileUtilityObj = NULL;
    }

    if(_ptrOutStream != NULL)
    {
        // If MediaFileImpl opened the OutStream it must be reclaimed here.
        if(_openFile)
        {
            delete _ptrOutStream;
            _openFile = false;
        }
        _ptrOutStream = NULL;
    }

    _recordingActive = false;
    codec_info_.pltype = 0;
    codec_info_.plname[0] = '\0';

    return 0;
}

bool MediaFileImpl::IsRecording()
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsRecording()");
    CriticalSectionScoped lock(_crit);
    return _recordingActive;
}

WebRtc_Word32 MediaFileImpl::RecordDurationMs(WebRtc_UWord32& durationMs)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
               "MediaFileImpl::RecordDurationMs()");

    CriticalSectionScoped lock(_crit);
    if(!_recordingActive)
    {
        durationMs = 0;
        return -1;
    }
    durationMs = _recordDurationMs;
    return 0;
}

bool MediaFileImpl::IsStereo()
{
    WEBRTC_TRACE(kTraceStream, kTraceFile, _id, "MediaFileImpl::IsStereo()");
    CriticalSectionScoped lock(_crit);
    return _isStereo;
}

WebRtc_Word32 MediaFileImpl::SetModuleFileCallback(FileCallback* callback)
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
                 "MediaFileImpl::SetModuleFileCallback(callback= 0x%x)",
                 &callback);

    CriticalSectionScoped lock(_callbackCrit);

    _ptrCallback = callback;
    return 0;
}

WebRtc_Word32 MediaFileImpl::FileDurationMs(const WebRtc_Word8* fileName,
                                            WebRtc_UWord32& durationMs,
                                            const FileFormats format,
                                            const WebRtc_UWord32 freqInHz)
{
    WEBRTC_TRACE(
        kTraceModuleCall,
        kTraceFile,
        _id,
        "MediaFileImpl::FileDurationMs(%s, -, format= %d, freqInHz= %d)",
        (fileName == NULL) ? "NULL" : fileName,
        format,
        freqInHz);

    if(!ValidFileName(fileName))
    {
        return -1;
    }
    if(!ValidFrequency(freqInHz))
    {
        return -1;
    }

    ModuleFileUtility* utilityObj = new ModuleFileUtility(_id);
    if(utilityObj == NULL)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "failed to allocate utility object!");
        return -1;
    }

    const WebRtc_Word32 duration = utilityObj->FileDurationMs(fileName, format,
                                                              freqInHz);
    delete utilityObj;
    if(duration == -1)
    {
        durationMs = 0;
        return -1;
    }

    durationMs = duration;
    return 0;
}

WebRtc_Word32 MediaFileImpl::PlayoutPositionMs(WebRtc_UWord32& positionMs) const
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
                 "MediaFileImpl::PlayoutPositionMS(?)");
    CriticalSectionScoped lock(_crit);
    if(!_playingActive)
    {
        positionMs = 0;
        return -1;
    }
    positionMs = _playoutPositionMs;
    return 0;
}

WebRtc_Word32 MediaFileImpl::codec_info(CodecInst& codecInst) const
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
               "MediaFileImpl::codec_info(CodecInst= 0x%x)", &codecInst);
    CriticalSectionScoped lock(_crit);
    if(!_playingActive && !_recordingActive)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "Neither playout nor recording has been initialized!");
        return -1;
    }
    if (codec_info_.pltype == 0 && codec_info_.plname[0] == '\0')
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "The CodecInst for %s is unknown!",
            _playingActive ? "Playback" : "Recording");
        return -1;
    }
    memcpy(&codecInst,&codec_info_,sizeof(CodecInst));
    return 0;
}

WebRtc_Word32 MediaFileImpl::VideoCodecInst(VideoCodec& codecInst) const
{
    WEBRTC_TRACE(kTraceModuleCall, kTraceFile, _id,
               "MediaFileImpl::VideoCodecInst(CodecInst= 0x%x)", &codecInst);
    CriticalSectionScoped lock(_crit);
    if(!_playingActive && !_recordingActive)
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, _id,
                     "Neither playout nor recording has been initialized!");
        return -1;
    }
    if( _ptrFileUtilityObj == NULL)
    {
        return -1;
    }
#ifdef WEBRTC_MODULE_UTILITY_VIDEO
    VideoCodec videoCodec;
    if( _ptrFileUtilityObj->VideoCodecInst( videoCodec) != 0)
    {
        return -1;
    }
    memcpy(&codecInst,&videoCodec,sizeof(VideoCodec));
    return 0;
#else
    return -1;
#endif
}

bool MediaFileImpl::ValidFileFormat(const FileFormats format,
                                    const CodecInst*  codecInst)
{
    if(codecInst == NULL)
    {
        if(format == kFileFormatPreencodedFile ||
           format == kFileFormatPcm8kHzFile    ||
           format == kFileFormatPcm16kHzFile   ||
           format == kFileFormatPcm32kHzFile)
        {
            WEBRTC_TRACE(kTraceError, kTraceFile, -1,
                         "Codec info required for file format specified!");
            return false;
        }
    }
    return true;
}

bool MediaFileImpl::ValidFileName(const WebRtc_Word8* fileName)
{
    if((fileName == NULL) ||(fileName[0] == '\0'))
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, -1, "FileName not specified!");
        return false;
    }
    return true;
}


bool MediaFileImpl::ValidFilePositions(const WebRtc_UWord32 startPointMs,
                                       const WebRtc_UWord32 stopPointMs)
{
    if(startPointMs == 0 && stopPointMs == 0) // Default values
    {
        return true;
    }
    if(stopPointMs &&(startPointMs >= stopPointMs))
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, -1,
                     "startPointMs must be less than stopPointMs!");
        return false;
    }
    if(stopPointMs &&((stopPointMs - startPointMs) < 20))
    {
        WEBRTC_TRACE(kTraceError, kTraceFile, -1,
                     "minimum play duration for files is 20 ms!");
        return false;
    }
    return true;
}

bool MediaFileImpl::ValidFrequency(const WebRtc_UWord32 frequency)
{
    if((frequency == 8000) || (frequency == 16000)|| (frequency == 32000))
    {
        return true;
    }
    WEBRTC_TRACE(kTraceError, kTraceFile, -1,
                 "Frequency should be 8000, 16000 or 32000 (Hz)");
    return false;
}
} // namespace webrtc
