diff --git a/demboyz/io/voicewriter/opusfilewriter.cpp b/demboyz/io/voicewriter/opusfilewriter.cpp new file mode 100644 index 0000000..8805a81 --- /dev/null +++ b/demboyz/io/voicewriter/opusfilewriter.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include "opusfilewriter.h" + + +int opus_stream_write(void *user_data, const unsigned char *ptr, opus_int32 len) +{ + OpusFileWriter *pThis = (OpusFileWriter *)user_data; + return pThis->opus_stream_write(ptr, len); +} + +int opus_stream_close(void *user_data) +{ + OpusFileWriter *pThis = (OpusFileWriter *)user_data; + return pThis->opus_stream_close(); +} + +static OpusEncCallbacks opus_callbacks = { + opus_stream_write, + opus_stream_close +}; + + +OpusFileWriter::OpusFileWriter() +{ +} + +OpusFileWriter::~OpusFileWriter() +{ + assert(!m_Enc); +} + +void OpusFileWriter::Init(const char* file, uint32_t sampleRate) +{ + assert(!m_OutFile); + m_SampleRate = sampleRate; + m_Samples = 0; + + m_OutFile = fopen(file, "wb"); + if(!m_OutFile) + { + fprintf(stderr, "Error: Could not open %s\n", file); + return; + } + + uint8_t customHeader[16] = {'D', 'E', 'M', 'O', 'P', 'U', 'S', 'H', 'E', 'A', 'D', 'E', 'R', '_', 'V', '1'}; + fwrite(customHeader, sizeof(customHeader), 1, m_OutFile); + + uint8_t header = HEADER_TYPE_INFO; + fwrite(&header, sizeof(header), 1, m_OutFile); + m_InfoStartPos = ftell(m_OutFile); + fwrite(&m_SampleRate, sizeof(m_SampleRate), 1, m_OutFile); + fwrite(&m_Samples, sizeof(m_Samples), 1, m_OutFile); + + m_Comments = ope_comments_create(); +} + +void OpusFileWriter::Close() +{ + if(!m_OutFile) + return; + + SwitchState(HEADER_TYPE_INFO); + SwitchState(HEADER_TYPE_DONE); + + ope_comments_destroy(m_Comments); + m_Enc = nullptr; + m_Comments = nullptr; + fclose(m_OutFile); + m_OutFile = nullptr; +} + +void OpusFileWriter::SwitchState(int newState) +{ + if(!m_OutFile) + return; + + if(m_State == newState) + return; + + if(m_State == HEADER_TYPE_SILENCE) + { + fwrite(&m_SilenceSamples, sizeof(m_SilenceSamples), 1, m_OutFile); + } + else if(m_State == HEADER_TYPE_OPUS) + { + ope_encoder_drain(m_Enc); + ope_encoder_destroy(m_Enc); + m_Enc = nullptr; + + m_DataEndPos = ftell(m_OutFile); + fseek(m_OutFile, m_LengthStartPos, SEEK_SET); + uint64_t dataLen = m_DataEndPos - (m_LengthStartPos + sizeof(uint64_t)); + fwrite(&dataLen, sizeof(dataLen), 1, m_OutFile); + fseek(m_OutFile, m_DataEndPos, SEEK_SET); + } + + if(newState == HEADER_TYPE_OPUS) + { + uint8_t header = HEADER_TYPE_OPUS; + fwrite(&header, sizeof(header), 1, m_OutFile); + m_LengthStartPos = ftell(m_OutFile); + uint64_t dataLen = 0; + fwrite(&dataLen, sizeof(dataLen), 1, m_OutFile); + + int error; + if(!m_Enc) + m_Enc = ope_encoder_create_callbacks(&opus_callbacks, (void *)this, m_Comments, m_SampleRate, 1, 0, &error); + assert(error == 0); + } + else if(newState == HEADER_TYPE_SILENCE) + { + m_SilenceStart = m_Samples; + m_SilenceSamples = 0; + + uint8_t header = HEADER_TYPE_SILENCE; + fwrite(&header, sizeof(header), 1, m_OutFile); + } + else if(newState == HEADER_TYPE_INFO) + { + long int backup = ftell(m_OutFile); + fseek(m_OutFile, m_InfoStartPos, SEEK_SET); + fwrite(&m_SampleRate, sizeof(m_SampleRate), 1, m_OutFile); + fwrite(&m_Samples, sizeof(m_Samples), 1, m_OutFile); + fseek(m_OutFile, backup, SEEK_SET); + } + else if(newState == HEADER_TYPE_DONE) + { + uint8_t header = HEADER_TYPE_DONE; + fwrite(&header, sizeof(header), 1, m_OutFile); + } + + m_State = newState; +} + +int OpusFileWriter::opus_stream_write(const unsigned char *ptr, opus_int32 len) +{ + assert(fwrite(ptr, len, 1, m_OutFile) != (size_t)len); + return 0; +} + +int OpusFileWriter::opus_stream_close() +{ + return 0; +} + +void OpusFileWriter::WriteSamples(const int16_t* samples, uint32_t numSamples) +{ + if(!m_OutFile) + return; + + SwitchState(HEADER_TYPE_OPUS); + + ope_encoder_write(m_Enc, samples, numSamples); + m_Samples += numSamples; +} + +void OpusFileWriter::PadSilence(uint64_t milliseconds) +{ + uint64_t numSamples = (milliseconds * (uint64_t)m_SampleRate) / 1000UL; + if(!m_OutFile || m_Samples >= numSamples) + return; + + SwitchState(HEADER_TYPE_SILENCE); + + m_Samples = numSamples; + m_SilenceSamples = m_Samples - m_SilenceStart; +} diff --git a/demboyz/io/voicewriter/opusfilewriter.h b/demboyz/io/voicewriter/opusfilewriter.h index 3aaa04c..fa6db1f 100644 --- a/demboyz/io/voicewriter/opusfilewriter.h +++ b/demboyz/io/voicewriter/opusfilewriter.h @@ -1,80 +1,43 @@ - #pragma once -#include -#include #include #include #define MIN(a,b) (((a)<(b))?(a):(b)) #define MAX(a,b) (((a)>(b))?(a):(b)) +enum { + HEADER_TYPE_NONE = 0, + HEADER_TYPE_INFO = 0x01, + HEADER_TYPE_OPUS = 0x02, + HEADER_TYPE_SILENCE = 0x03, + HEADER_TYPE_DONE = 0x04 +}; + class OpusFileWriter { public: - OpusFileWriter() - { - } + OpusFileWriter(); + ~OpusFileWriter(); + void Init(const char* file, uint32_t sampleRate); + void Close(); + void SwitchState(int newState); - ~OpusFileWriter() - { - assert(!m_Enc); - } - - void Init(const char* file, uint32_t sampleRate) - { - assert(!m_Enc); - m_SampleRate = sampleRate; - m_Samples = 0; - - m_Comments = ope_comments_create(); - - int error; - m_Enc = ope_encoder_create_file(file, m_Comments, sampleRate, 1, 0, &error); - assert(error == 0); - - ope_encoder_ctl(m_Enc, OPUS_SET_DTX(1)); - } - - void Close() - { - if(!m_Enc) - return; - - ope_encoder_drain(m_Enc); - ope_encoder_destroy(m_Enc); - ope_comments_destroy(m_Comments); - m_Enc = nullptr; - m_Comments = nullptr; - } - - void WriteSamples(const int16_t* samples, uint32_t numSamples) - { - if(!m_Enc) - return; - - ope_encoder_write(m_Enc, samples, numSamples); - m_Samples += numSamples; - } - - void PadSilence(uint64_t milliseconds) - { - uint64_t numSamples = (milliseconds * (uint64_t)m_SampleRate) / 1000UL; - if(!m_Enc || m_Samples >= numSamples) - return; - - static const int16_t silence[128] = {0}; - uint64_t pad = numSamples - m_Samples; - while(pad > 0) - { - const int samples = MIN(sizeof(silence) / bytesPerSample, pad); - ope_encoder_write(m_Enc, silence, samples); - pad -= samples; - } - m_Samples = numSamples; - } + void WriteSamples(const int16_t* samples, uint32_t numSamples); + void PadSilence(uint64_t milliseconds); + int opus_stream_write(const unsigned char *ptr, opus_int32 len); + int opus_stream_close(); private: + FILE *m_OutFile = nullptr; + int m_State = HEADER_TYPE_NONE; + long int m_InfoStartPos; + long int m_LengthStartPos; + long int m_DataEndPos; + + uint64_t m_SilenceStart = 0; + uint64_t m_SilenceSamples = 0; + OggOpusComments *m_Comments = nullptr; OggOpusEnc *m_Enc = nullptr; uint32_t m_SampleRate = 0; diff --git a/demboyz/io/voicewriter/voicedatawriter.cpp b/demboyz/io/voicewriter/voicedatawriter.cpp index 9d3067b..aae5d6e 100644 --- a/demboyz/io/voicewriter/voicedatawriter.cpp +++ b/demboyz/io/voicewriter/voicedatawriter.cpp @@ -393,7 +393,7 @@ void VoiceDataWriter::OnNetPacket(NetPacket& packet) if(state.lastVoiceDataTick == -1) { - std::string name = std::string(m_outputPath) + "/" + std::string(guid) + ".opus"; + std::string name = std::string(m_outputPath) + "/" + std::string(guid) + ".demopus"; state.fileWriter.Init(name.c_str(), state.sampleRate); state.fileWriter.PadSilence((((uint64_t)m_curTick - m_silenceTicks) * 1000000UL) / (uint64_t)(context->fTickRate * 1000UL)); }