first commit
This commit is contained in:
140
src/AudioWriters/OggAudioWriter.h
Normal file
140
src/AudioWriters/OggAudioWriter.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <ogg/ogg.h>
|
||||
#include <mutex>
|
||||
#include "Concepts/AudioWriterConcept.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace snoop {
|
||||
|
||||
class OggAudioWriter {
|
||||
std::ofstream m_outputFile;
|
||||
ogg_stream_state m_oggStream = {};
|
||||
bool m_isInWriting = false;
|
||||
long m_packetNumber = 0;
|
||||
int64_t m_granulePos = 0;
|
||||
int m_sampleRate;
|
||||
int m_channels;
|
||||
std::mutex m_mutex;
|
||||
|
||||
public:
|
||||
OggAudioWriter( int sampleRate, int channels )
|
||||
: m_sampleRate( sampleRate ), m_channels( channels ) {
|
||||
}
|
||||
|
||||
~OggAudioWriter() {
|
||||
this->StopWriting();
|
||||
}
|
||||
|
||||
void StartWriting( const std::string& filePath ) {
|
||||
std::lock_guard l( this->m_mutex );
|
||||
StartWritingInternal( filePath );
|
||||
}
|
||||
|
||||
void StopWriting() {
|
||||
std::lock_guard l( this->m_mutex );
|
||||
StopWritingInternal();
|
||||
}
|
||||
|
||||
void Write( const char* data, size_t size, size_t frames ) {
|
||||
std::lock_guard l( this->m_mutex );
|
||||
WriteInternal( data, size, frames );
|
||||
}
|
||||
|
||||
private:
|
||||
void StartWritingInternal( std::string filePath ) {
|
||||
if( this->m_isInWriting ) {
|
||||
this->StopWritingInternal();
|
||||
}
|
||||
m_isInWriting = true;
|
||||
|
||||
this->m_packetNumber = 0;
|
||||
this->m_granulePos = 0;
|
||||
this->m_oggStream = {};
|
||||
this->m_outputFile = {};
|
||||
|
||||
ogg_stream_init( &this->m_oggStream, 1 );
|
||||
this->m_outputFile.open( filePath, std::ios::binary );
|
||||
if( !this->m_outputFile.is_open() ) {
|
||||
throw std::runtime_error( "Failed to open file for writing: " + filePath );
|
||||
}
|
||||
|
||||
this->WriteOpusHeader();
|
||||
this->WriteOpusTags();
|
||||
|
||||
spdlog::info( "Started writing Ogg audio to {}", filePath );
|
||||
}
|
||||
|
||||
void StopWritingInternal() {
|
||||
if( !m_isInWriting ) {
|
||||
return;
|
||||
}
|
||||
if( this->m_outputFile.is_open() ) {
|
||||
ogg_packet packet = {};
|
||||
packet.e_o_s = 1;
|
||||
packet.granulepos = this->m_granulePos;
|
||||
packet.packetno = this->m_packetNumber++;
|
||||
ogg_stream_packetin( &m_oggStream, &packet );
|
||||
this->WriteOggPages( true );
|
||||
|
||||
this->m_outputFile.close();
|
||||
spdlog::info( "Stopped writing Ogg audio" );
|
||||
}
|
||||
ogg_stream_clear( &this->m_oggStream );
|
||||
this->m_isInWriting = false;
|
||||
}
|
||||
|
||||
void WriteInternal( const char* data, size_t size, size_t frames ) {
|
||||
if( !this->m_isInWriting ) {
|
||||
return;
|
||||
}
|
||||
|
||||
ogg_packet packet;
|
||||
packet.packet = reinterpret_cast<unsigned char*>(const_cast<char*>(data));
|
||||
packet.bytes = static_cast<long>(size);
|
||||
packet.b_o_s = this->m_packetNumber == 0 ? 1 : 0;
|
||||
packet.e_o_s = 0;
|
||||
packet.granulepos = this->m_granulePos += static_cast<ssize_t>(frames) * m_channels;
|
||||
packet.packetno = this->m_packetNumber++;
|
||||
|
||||
ogg_stream_packetin( &this->m_oggStream, &packet );
|
||||
this->WriteOggPages();
|
||||
}
|
||||
|
||||
void WriteOpusHeader() {
|
||||
static constexpr char magic[ ] = "OpusHead";
|
||||
unsigned char header[ 19 ] = { 0 };
|
||||
memcpy( header, reinterpret_cast<const void*>(magic), 8 );
|
||||
header[ 8 ] = 1; // version
|
||||
header[ 9 ] = m_channels;
|
||||
*reinterpret_cast<int*>( &header[ 12 ] ) = m_sampleRate;
|
||||
|
||||
this->WriteInternal( reinterpret_cast<const char*>(header), sizeof( header ), 0 );
|
||||
}
|
||||
|
||||
void WriteOpusTags() {
|
||||
constexpr char tags[ ] = "OpusTags\0\0\0\0\0\0\0\0";
|
||||
this->WriteInternal( tags, sizeof( tags ) - 1, 0 );
|
||||
}
|
||||
|
||||
void WriteOggPages( bool flush = false ) {
|
||||
ogg_page page;
|
||||
while( ogg_stream_pageout( &m_oggStream, &page ) ) {
|
||||
this->m_outputFile.write( reinterpret_cast<const char*>(page.header), page.header_len );
|
||||
this->m_outputFile.write( reinterpret_cast<const char*>(page.body), page.body_len );
|
||||
}
|
||||
if( flush ) {
|
||||
while( ogg_stream_flush( &m_oggStream, &page ) ) {
|
||||
this->m_outputFile.write( reinterpret_cast<const char*>(page.header), page.header_len );
|
||||
this->m_outputFile.write( reinterpret_cast<const char*>(page.body), page.body_len );
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert( AudioWriterConcept<OggAudioWriter>, "OggAudioWriter does not satisfy AudioWriterConcept" );
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user