Merge branch 'rr1-maint'
Conflicts: include/core/inthread.hpp src/core/inthread.cpp
This commit is contained in:
commit
adea6ff6cb
8 changed files with 1705 additions and 215 deletions
|
@ -6,9 +6,16 @@
|
|||
#include <string>
|
||||
|
||||
void voicethread_task();
|
||||
void voicethread_task_end();
|
||||
void voicethread_kill();
|
||||
void voice_frame_number(uint64_t newframe, double rate);
|
||||
|
||||
enum external_stream_format
|
||||
{
|
||||
EXTFMT_OPUSDEMO,
|
||||
EXTFMT_SOX,
|
||||
EXTFMT_OGGOPUS
|
||||
};
|
||||
|
||||
struct playback_stream_info
|
||||
{
|
||||
uint64_t id;
|
||||
|
@ -19,8 +26,8 @@ struct playback_stream_info
|
|||
bool voicesub_collection_loaded();
|
||||
std::list<playback_stream_info> voicesub_get_stream_info();
|
||||
void voicesub_play_stream(uint64_t id);
|
||||
void voicesub_export_stream(uint64_t id, const std::string& filename, bool opus);
|
||||
uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, bool opus);
|
||||
void voicesub_export_stream(uint64_t id, const std::string& filename, external_stream_format fmt);
|
||||
uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, external_stream_format fmt);
|
||||
void voicesub_delete_stream(uint64_t id);
|
||||
void voicesub_export_superstream(const std::string& filename);
|
||||
void voicesub_load_collection(const std::string& filename);
|
||||
|
|
333
include/library/ogg.hpp
Normal file
333
include/library/ogg.hpp
Normal file
|
@ -0,0 +1,333 @@
|
|||
#ifndef _library__ogg__hpp__included__
|
||||
#define _library__ogg__hpp__included__
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
/**
|
||||
* A page in Ogg bitstream.
|
||||
*/
|
||||
class ogg_page
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a new blank page.
|
||||
*/
|
||||
ogg_page() throw();
|
||||
/**
|
||||
* Create a page, reading a buffer.
|
||||
*
|
||||
* Parameter buffer: The buffer to read.
|
||||
* Parameter advance: The number of bytes in packet is stored here.
|
||||
* Throws std::runtime_error: Bad packet.
|
||||
*/
|
||||
ogg_page(const char* buffer, size_t& advance) throw(std::runtime_error);
|
||||
/**
|
||||
* Scan a buffer for pages.
|
||||
*
|
||||
* Parameter buffer: The buffer to scan.
|
||||
* Parameter bufferlen: The length of buffer. Should be at least 65307, unless there's not that much available.
|
||||
* Parameter eof: If set, assume physical stream ends after this buffer.
|
||||
* Parameter advance: Amount to advance the pointer is stored here.
|
||||
* Returns: True if packet was found, false if not.
|
||||
*/
|
||||
static bool scan(const char* buffer, size_t bufferlen, bool eof, size_t& advance) throw();
|
||||
/**
|
||||
* Get the continue flag of packet.
|
||||
*/
|
||||
bool get_continue() const throw() { return flag_continue; }
|
||||
/**
|
||||
* Set the continue flag of packet.
|
||||
*/
|
||||
void set_continue(bool c) throw() { flag_continue = c; }
|
||||
/**
|
||||
* Get the BOS flag of packet.
|
||||
*/
|
||||
bool get_bos() const throw() { return flag_bos; }
|
||||
/**
|
||||
* Set the BOS flag of packet.
|
||||
*/
|
||||
void set_bos(bool b) throw() { flag_bos = b; }
|
||||
/**
|
||||
* Get the EOS flag of packet.
|
||||
*/
|
||||
bool get_eos() const throw() { return flag_eos; }
|
||||
/**
|
||||
* Set the EOS flag of packet.
|
||||
*/
|
||||
void set_eos(bool e) throw() { flag_eos = e; }
|
||||
/**
|
||||
* Get the granulepos of packet.
|
||||
*/
|
||||
uint64_t get_granulepos() const throw() { return granulepos; }
|
||||
/**
|
||||
* Set the granulepos of packet.
|
||||
*/
|
||||
void set_granulepos(uint64_t g) throw() { granulepos = g; }
|
||||
/**
|
||||
* Get stream identifier.
|
||||
*/
|
||||
uint32_t get_stream() const throw() { return stream; }
|
||||
/**
|
||||
* Set stream identifier.
|
||||
*/
|
||||
void set_stream(uint32_t s) throw() { stream = s; }
|
||||
/**
|
||||
* Get stream identifier.
|
||||
*/
|
||||
uint32_t get_sequence() const throw() { return sequence; }
|
||||
/**
|
||||
* Set stream identifier.
|
||||
*/
|
||||
void set_sequence(uint32_t s) throw() { sequence = s; }
|
||||
/**
|
||||
* Get number of packets.
|
||||
*/
|
||||
uint8_t get_packet_count() const throw() { return packet_count; }
|
||||
/**
|
||||
* Get the packet.
|
||||
*/
|
||||
std::pair<const uint8_t*, size_t> get_packet(size_t packetno) const throw()
|
||||
{
|
||||
if(packetno >= packet_count)
|
||||
return std::make_pair(reinterpret_cast<const uint8_t*>(NULL), 0);
|
||||
else
|
||||
return std::make_pair(data + packets[packetno], packets[packetno + 1] - packets[packetno]);
|
||||
}
|
||||
/**
|
||||
* Get the last packet incomplete flag.
|
||||
*/
|
||||
bool get_last_packet_incomplete() const throw() { return last_incomplete; }
|
||||
/**
|
||||
* Append a complete packet to page.
|
||||
*
|
||||
* Parameter d: The data to append.
|
||||
* Parameter dlen: The length of data to append.
|
||||
* Returns: True on success, false on failure.
|
||||
*/
|
||||
bool append_packet(const uint8_t* d, size_t dlen) throw();
|
||||
/**
|
||||
* Append a possibly incomplete packet to page.
|
||||
*
|
||||
* Parameter d: The data to append. Adjusted.
|
||||
* Parameter dlen: The length of data to append. Adjusted
|
||||
* Returns: True if write was complete, false if incomplete.
|
||||
*/
|
||||
bool append_packet_incomplete(const uint8_t*& d, size_t& dlen) throw();
|
||||
/**
|
||||
* Get number of octets it takes to serialize this.
|
||||
*/
|
||||
size_t serialize_size() const throw() { return 27 + segment_count + data_count; }
|
||||
/**
|
||||
* Serialize this packet.
|
||||
*
|
||||
* Parameter buffer: Buffer to serialize to (use serialize_size() to find the size).
|
||||
*/
|
||||
void serialize(char* buffer) const throw();
|
||||
/**
|
||||
* The special granule pos for nothing.
|
||||
*/
|
||||
const static uint64_t granulepos_none;
|
||||
private:
|
||||
uint8_t version;
|
||||
bool flag_continue;
|
||||
bool flag_bos;
|
||||
bool flag_eos;
|
||||
bool last_incomplete;
|
||||
uint64_t granulepos;
|
||||
uint32_t stream;
|
||||
uint32_t sequence;
|
||||
uint8_t segment_count;
|
||||
uint8_t packet_count;
|
||||
uint16_t data_count;
|
||||
uint8_t data[65025];
|
||||
uint8_t segments[255];
|
||||
uint16_t packets[256];
|
||||
};
|
||||
|
||||
/**
|
||||
* Ogg stream reader.
|
||||
*/
|
||||
class ogg_stream_reader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ogg_stream_reader() throw();
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~ogg_stream_reader() throw();
|
||||
/**
|
||||
* Read some data.
|
||||
*
|
||||
* Parameter buffer: The buffer to store the data to.
|
||||
* Parameter size: The maximum size to read.
|
||||
* Returns: The number of bytes actually read.
|
||||
*/
|
||||
virtual size_t read(char* buffer, size_t size) throw(std::exception) = 0;
|
||||
/**
|
||||
* Read a page from stream.
|
||||
*
|
||||
* Parameter page: The page is assigned here if successful.
|
||||
* Returns: True if page was obtained, false if not.
|
||||
*/
|
||||
bool get_page(ogg_page& page) throw(std::exception);
|
||||
/**
|
||||
* Set stream to report errors to.
|
||||
*
|
||||
* Parameter strm: The stream.
|
||||
*/
|
||||
void set_errors_to(std::ostream& os);
|
||||
private:
|
||||
ogg_stream_reader(const ogg_stream_reader&);
|
||||
ogg_stream_reader& operator=(const ogg_stream_reader&);
|
||||
void fill_buffer();
|
||||
void discard_buffer(size_t amount);
|
||||
bool eof;
|
||||
char buffer[65536];
|
||||
size_t left;
|
||||
std::ostream* errors_to;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ogg stream reader based on std::istream.
|
||||
*/
|
||||
class ogg_stream_reader_iostreams : public ogg_stream_reader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Parameter stream: The stream to read the data from.
|
||||
*/
|
||||
ogg_stream_reader_iostreams(std::istream& stream);
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~ogg_stream_reader_iostreams() throw();
|
||||
|
||||
size_t read(char* buffer, size_t size) throw(std::exception);
|
||||
private:
|
||||
std::istream& is;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ogg stream writer.
|
||||
*/
|
||||
class ogg_stream_writer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ogg_stream_writer() throw();
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
virtual ~ogg_stream_writer() throw();
|
||||
/**
|
||||
* Write data.
|
||||
*
|
||||
* Parameter data: The data to write.
|
||||
* Parameter size: The size to write.
|
||||
*/
|
||||
virtual void write(const char* buffer, size_t size) throw(std::exception) = 0;
|
||||
/**
|
||||
* Write a page to stream.
|
||||
*
|
||||
* Parameter page: The page to write.
|
||||
*/
|
||||
void put_page(const ogg_page& page) throw(std::exception);
|
||||
private:
|
||||
ogg_stream_writer(const ogg_stream_writer&);
|
||||
ogg_stream_writer& operator=(const ogg_stream_writer&);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ogg stream writer based on std::istream.
|
||||
*/
|
||||
class ogg_stream_writer_iostreams : public ogg_stream_writer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Parameter stream: The stream to read the data from.
|
||||
*/
|
||||
ogg_stream_writer_iostreams(std::ostream& stream);
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~ogg_stream_writer_iostreams() throw();
|
||||
|
||||
void write(const char* buffer, size_t size) throw(std::exception);
|
||||
private:
|
||||
std::ostream& os;
|
||||
};
|
||||
|
||||
/**
|
||||
* OggOpus header structure.
|
||||
*/
|
||||
struct oggopus_header
|
||||
{
|
||||
uint8_t version;
|
||||
uint8_t channels;
|
||||
uint16_t preskip;
|
||||
uint32_t rate;
|
||||
int16_t gain;
|
||||
uint8_t map_family;
|
||||
uint8_t streams;
|
||||
uint8_t coupled;
|
||||
uint8_t chanmap[255];
|
||||
};
|
||||
|
||||
/**
|
||||
* OggOpus tags structure
|
||||
*/
|
||||
struct oggopus_tags
|
||||
{
|
||||
std::string vendor;
|
||||
std::vector<std::string> comments;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse Ogg page as OggOpus header.
|
||||
*
|
||||
* Parameter page: The page to parse.
|
||||
* Returns: Parsed data.
|
||||
* Throws std::runtime_error: Not valid OggOpus header page.
|
||||
*/
|
||||
struct oggopus_header parse_oggopus_header(struct ogg_page& page) throw(std::runtime_error);
|
||||
|
||||
/**
|
||||
* Serialize OggOpus header as an Ogg page.
|
||||
*
|
||||
* Parameter header: The header.
|
||||
* Returns: The serialized page.
|
||||
* Throws std::runtime_error: Not valid OggOpus header.
|
||||
*/
|
||||
struct ogg_page serialize_oggopus_header(struct oggopus_header& header) throw(std::runtime_error);
|
||||
|
||||
/**
|
||||
* Parse Ogg page as OggOpus comment.
|
||||
*
|
||||
* Parameter page: The page to parse.
|
||||
* Returns: Parsed data.
|
||||
* Throws std::runtime_error: Not valid OggOpus comment page.
|
||||
*/
|
||||
struct oggopus_tags parse_oggopus_tags(struct ogg_page& page) throw(std::bad_alloc, std::runtime_error);
|
||||
|
||||
/**
|
||||
* Serialize OggOpus comments as an Ogg page.
|
||||
*
|
||||
* Parameter tags: The comments.
|
||||
* Returns: The serialized page.
|
||||
* Throws std::runtime_error: Not valid OggOpus comments.
|
||||
*/
|
||||
struct ogg_page serialize_oggopus_tags(struct oggopus_tags& tags) throw(std::runtime_error);
|
||||
|
||||
#endif
|
|
@ -1,3 +1,4 @@
|
|||
#include <lsnes.hpp>
|
||||
#include <cstdint>
|
||||
#ifdef WITH_OPUS_CODEC
|
||||
#include "library/filesys.hpp"
|
||||
|
@ -5,6 +6,7 @@
|
|||
#include "library/workthread.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "library/string.hpp"
|
||||
#include "library/ogg.hpp"
|
||||
#include "core/audioapi.hpp"
|
||||
#include "core/command.hpp"
|
||||
#include "core/framerate.hpp"
|
||||
|
@ -18,6 +20,7 @@
|
|||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <zlib.h>
|
||||
//Fsck it.
|
||||
#define OPUS_BUILD
|
||||
#include "opus/opus.h"
|
||||
|
@ -41,10 +44,14 @@
|
|||
#define ITERATION_TIME 15000
|
||||
//Opus bitrate to use.
|
||||
#define OPUS_BITRATE 48000
|
||||
//Ogg Opus granule rate.
|
||||
#define OGGOPUS_GRANULERATE 48000
|
||||
//Record buffer size threshold divider.
|
||||
#define REC_THRESHOLD_DIV 40
|
||||
//Playback buffer size threshold divider.
|
||||
#define PLAY_THRESHOLD_DIV 30
|
||||
//Special granule position: None.
|
||||
#define GRANULEPOS_NONE 0xFFFFFFFFFFFFFFFFULL
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -124,12 +131,13 @@ namespace
|
|||
opus_stream(uint64_t base, filesystem_ref filesys, uint32_t ctrl_cluster, uint32_t data_cluster);
|
||||
//Import a stream with specified base time.
|
||||
//Can throw.
|
||||
opus_stream(uint64_t base, filesystem_ref filesys, std::ifstream& data, bool compressed);
|
||||
opus_stream(uint64_t base, filesystem_ref filesys, std::ifstream& data,
|
||||
external_stream_format extfmt);
|
||||
//Delete this stream (also puts a ref)
|
||||
void delete_stream() { deleting = true; put_ref(); }
|
||||
//Export a stream.
|
||||
//Can throw.
|
||||
void export_stream(std::ofstream& data, bool compressed);
|
||||
void export_stream(std::ofstream& data, external_stream_format extfmt);
|
||||
//Get length of specified packet in samples.
|
||||
uint16_t packet_length(uint32_t seqno)
|
||||
{
|
||||
|
@ -146,7 +154,27 @@ namespace
|
|||
//Set base time in samples for stream.
|
||||
void timebase(uint64_t ts) { s_timebase = ts; }
|
||||
//Get length of stream in samples.
|
||||
uint64_t length() { return total_len; }
|
||||
uint64_t length()
|
||||
{
|
||||
if(pregap_length + postgap_length > total_len)
|
||||
return 0;
|
||||
else
|
||||
return total_len - pregap_length - postgap_length;
|
||||
}
|
||||
//Set the pregap length.
|
||||
void set_pregap(uint32_t p) { pregap_length = p; }
|
||||
//Get the pregap length.
|
||||
uint32_t get_pregap() { return pregap_length; }
|
||||
//Set the postgap length.
|
||||
void set_potsgap(uint32_t p) { postgap_length = p; }
|
||||
//Get the postgap length.
|
||||
uint32_t get_postgap() { return postgap_length; }
|
||||
//Set gain.
|
||||
void set_gain(int16_t g) { gain = g; }
|
||||
//Get gain.
|
||||
int16_t get_gain() { return gain; }
|
||||
//Get linear gain.
|
||||
float get_gain_linear() { return pow(10, gain / 20); }
|
||||
//Get number of packets in stream.
|
||||
uint32_t blocks() { return packets.size(); }
|
||||
//Is this stream locked?
|
||||
|
@ -163,9 +191,18 @@ namespace
|
|||
//Not safe to call simultaneously with packet_length() or packet().
|
||||
//Can throw.
|
||||
void write(uint8_t len, const unsigned char* payload, size_t payload_len);
|
||||
//Write stream trailer.
|
||||
void write_trailier();
|
||||
//Get clusters.
|
||||
std::pair<uint32_t, uint32_t> get_clusters() { return std::make_pair(ctrl_cluster, data_cluster); }
|
||||
private:
|
||||
void export_stream_opusdemo(std::ofstream& data);
|
||||
void export_stream_sox(std::ofstream& data);
|
||||
void export_stream_oggopus(std::ofstream& data);
|
||||
void import_stream_opusdemo(std::ifstream& data);
|
||||
void import_stream_sox(std::ifstream& data);
|
||||
void import_stream_oggopus(std::ifstream& data);
|
||||
|
||||
opus_stream(const opus_stream&);
|
||||
opus_stream& operator=(const opus_stream&);
|
||||
void destroy();
|
||||
|
@ -179,6 +216,9 @@ namespace
|
|||
uint32_t next_moffset;
|
||||
uint32_t ctrl_cluster;
|
||||
uint32_t data_cluster;
|
||||
uint32_t pregap_length;
|
||||
uint32_t postgap_length;
|
||||
int16_t gain;
|
||||
bool locked;
|
||||
mutex_class reflock;
|
||||
unsigned refcount;
|
||||
|
@ -199,6 +239,9 @@ namespace
|
|||
next_moffset = 0;
|
||||
ctrl_cluster = 0;
|
||||
data_cluster = 0;
|
||||
pregap_length = 0;
|
||||
postgap_length = 0;
|
||||
gain = 0;
|
||||
}
|
||||
|
||||
opus_stream::opus_stream(uint64_t base, filesystem_ref filesys, uint32_t _ctrl_cluster,
|
||||
|
@ -214,11 +257,18 @@ namespace
|
|||
next_mcluster = ctrl_cluster = _ctrl_cluster;
|
||||
next_offset = 0;
|
||||
next_moffset = 0;
|
||||
pregap_length = 0;
|
||||
postgap_length = 0;
|
||||
gain = 0;
|
||||
//Read the data buffers.
|
||||
char buf[CLUSTER_SIZE];
|
||||
uint32_t last_cluster_seen = next_mcluster;
|
||||
uint64_t total_size = 0;
|
||||
uint64_t total_frames = 0;
|
||||
bool trailers = false;
|
||||
bool saved_pointer_valid = false;
|
||||
uint32_t saved_next_mcluster = 0;
|
||||
uint32_t saved_next_moffset = 0;
|
||||
while(true) {
|
||||
last_cluster_seen = next_mcluster;
|
||||
size_t r = fs.read_data(next_mcluster, next_moffset, buf, CLUSTER_SIZE);
|
||||
|
@ -228,10 +278,32 @@ namespace
|
|||
}
|
||||
//Find the first unused entry if any.
|
||||
for(unsigned i = 0; i < CLUSTER_SIZE; i += 4)
|
||||
if(!buf[i + 3]) {
|
||||
//This entry is unused, end of stream.
|
||||
next_moffset = i;
|
||||
goto out_parsing;
|
||||
if(!buf[i + 3] || trailers) {
|
||||
//This entry is unused. If the next entry is also unused, that is the end.
|
||||
//Otherwise, there might be stream trailers.
|
||||
if(trailers && !buf[i + 3]) {
|
||||
goto out_parsing; //Ends for real.
|
||||
}
|
||||
if(!trailers) {
|
||||
//Set the trailer flag and continue parsing.
|
||||
//The saved offset must be placed here.
|
||||
saved_next_mcluster = last_cluster_seen;
|
||||
saved_next_moffset = i;
|
||||
saved_pointer_valid = true;
|
||||
trailers = true;
|
||||
continue;
|
||||
}
|
||||
//This is a trailer entry.
|
||||
if(buf[i + 3] == 2) {
|
||||
//Pregap.
|
||||
pregap_length = read32ube(buf + i) >> 8;
|
||||
} else if(buf[i + 3] == 3) {
|
||||
//Postgap.
|
||||
postgap_length = read32ube(buf + i) >> 8;
|
||||
} else if(buf[i + 3] == 4) {
|
||||
//Gain.
|
||||
gain = read16sbe(buf + i);
|
||||
}
|
||||
} else {
|
||||
uint16_t psize = read16ube(buf + i);
|
||||
uint8_t plen = read8ube(buf + i + 2);
|
||||
|
@ -247,10 +319,15 @@ namespace
|
|||
}
|
||||
}
|
||||
out_parsing:
|
||||
;
|
||||
//If saved pointer is valid, restore to that.
|
||||
if(saved_pointer_valid) {
|
||||
next_mcluster = saved_next_mcluster;
|
||||
next_moffset = saved_next_moffset;
|
||||
}
|
||||
}
|
||||
|
||||
opus_stream::opus_stream(uint64_t base, filesystem_ref filesys, std::ifstream& data, bool compressed)
|
||||
opus_stream::opus_stream(uint64_t base, filesystem_ref filesys, std::ifstream& data,
|
||||
external_stream_format extfmt)
|
||||
: fs(filesys)
|
||||
{
|
||||
refcount = 1;
|
||||
|
@ -264,10 +341,22 @@ out_parsing:
|
|||
next_moffset = 0;
|
||||
ctrl_cluster = 0;
|
||||
data_cluster = 0;
|
||||
pregap_length = 0;
|
||||
postgap_length = 0;
|
||||
gain = 0;
|
||||
if(extfmt == EXTFMT_OPUSDEMO)
|
||||
import_stream_opusdemo(data);
|
||||
else if(extfmt == EXTFMT_OGGOPUS)
|
||||
import_stream_oggopus(data);
|
||||
else if(extfmt == EXTFMT_SOX)
|
||||
import_stream_sox(data);
|
||||
}
|
||||
|
||||
void opus_stream::import_stream_opusdemo(std::ifstream& data)
|
||||
{
|
||||
int err;
|
||||
unsigned char tmpi[65536];
|
||||
float tmp[OPUS_MAX_OUT];
|
||||
if(compressed) {
|
||||
OpusDecoder* dec = opus_decoder_create(48000, 1, &err);
|
||||
while(data) {
|
||||
char head[8];
|
||||
|
@ -323,7 +412,237 @@ out_parsing:
|
|||
}
|
||||
}
|
||||
opus_decoder_destroy(dec);
|
||||
try {
|
||||
write_trailier();
|
||||
} catch(...) {
|
||||
if(ctrl_cluster) fs.free_cluster_chain(ctrl_cluster);
|
||||
if(data_cluster) fs.free_cluster_chain(data_cluster);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
class oggopus_importer
|
||||
{
|
||||
public:
|
||||
struct packet
|
||||
{
|
||||
const unsigned char* data;
|
||||
size_t size;
|
||||
unsigned units;
|
||||
};
|
||||
oggopus_importer();
|
||||
void put_page(const ogg_page& p);
|
||||
bool packet_pending();
|
||||
packet get_packet();
|
||||
size_t get_postgap();
|
||||
private:
|
||||
oggopus_importer(const oggopus_importer&);
|
||||
oggopus_importer& operator=(const oggopus_importer&);
|
||||
std::vector<uint8_t> incomplete;
|
||||
std::vector<uint8_t> pincomplete;
|
||||
const ogg_page* curpage;
|
||||
size_t curpacket;
|
||||
size_t pagepackets;
|
||||
size_t postgap;
|
||||
bool eospage;
|
||||
bool incomplete_f;
|
||||
uint64_t expected_granule;
|
||||
bool granule_correction_done;
|
||||
};
|
||||
|
||||
oggopus_importer::oggopus_importer()
|
||||
{
|
||||
curpacket = 0;
|
||||
curpage = 0;
|
||||
pagepackets = 0;
|
||||
postgap = 0;
|
||||
eospage = false;
|
||||
incomplete_f = false;
|
||||
expected_granule = 0;
|
||||
granule_correction_done = false;
|
||||
}
|
||||
|
||||
void oggopus_importer::put_page(const ogg_page& p)
|
||||
{
|
||||
//If not continued packet, drop the last packet.
|
||||
if(!p.get_continue() && incomplete_f)
|
||||
messages << "Warning: Incomplete packet not continued by the next page" << std::endl;
|
||||
if(!p.get_continue())
|
||||
incomplete.clear();
|
||||
//If totally empty page...
|
||||
if(p.get_packet_count() == 0) {
|
||||
if(p.get_granulepos() != ogg_page::granulepos_none)
|
||||
(stringfmt() << "Bad granulepos in empty page: Expected -1, got "
|
||||
<< p.get_granulepos()).throwex();
|
||||
curpacket = 0;
|
||||
pagepackets = 0;
|
||||
curpage = &p;
|
||||
return;
|
||||
}
|
||||
//If continued packet, paste packet 0 to previous.
|
||||
size_t tmppackets = p.get_packet_count();
|
||||
bool tmp_incomplete;
|
||||
if((tmp_incomplete = p.get_last_packet_incomplete()))
|
||||
tmppackets--;
|
||||
if(tmppackets == 0 && p.get_granulepos() != ogg_page::granulepos_none)
|
||||
(stringfmt() << "Bad granulepos in page with no complete packets: Expected -1, got "
|
||||
<< p.get_granulepos()).throwex();
|
||||
if(tmppackets > 0 && p.get_granulepos() == ogg_page::granulepos_none)
|
||||
(stringfmt() << "Bad granulepos in page with complete packets: Expected != -1, got "
|
||||
<< "-1").throwex();
|
||||
auto p0 = p.get_packet(0);
|
||||
size_t off = incomplete.size();
|
||||
incomplete.resize(off + p0.second);
|
||||
if(p0.second)
|
||||
memcpy(&incomplete[off], p0.first, p0.second);
|
||||
incomplete_f = tmp_incomplete;
|
||||
pagepackets = tmppackets;
|
||||
curpacket = 0;
|
||||
curpage = &p;
|
||||
eospage = p.get_eos();
|
||||
}
|
||||
|
||||
bool oggopus_importer::packet_pending()
|
||||
{
|
||||
return (curpacket < pagepackets);
|
||||
}
|
||||
|
||||
oggopus_importer::packet oggopus_importer::get_packet()
|
||||
{
|
||||
oggopus_importer::packet p;
|
||||
if(curpacket == 0) {
|
||||
//Packet 0 is special.
|
||||
pincomplete = incomplete;
|
||||
p.data = &pincomplete[0];
|
||||
p.size = pincomplete.size();
|
||||
} else {
|
||||
auto pn = curpage->get_packet(curpacket);
|
||||
p.data = pn.first;
|
||||
p.size = pn.second;
|
||||
}
|
||||
curpacket++;
|
||||
if(curpacket == pagepackets && incomplete_f) {
|
||||
//Copy the incomplete page to buffer.
|
||||
auto pn = curpage->get_packet(pagepackets);
|
||||
incomplete.resize(pn.second);
|
||||
if(pn.second)
|
||||
memcpy(&incomplete[0], pn.first, pn.second);
|
||||
}
|
||||
if(!p.size) {
|
||||
messages << "Warning, skipping empty Opus packet" << std::endl;
|
||||
p.units = 0;
|
||||
} else {
|
||||
int frames = opus_packet_get_nb_frames(p.data, p.size);
|
||||
int samples_pf = opus_packet_get_samples_per_frame(p.data, OGGOPUS_GRANULERATE);
|
||||
if(frames < 0 || samples_pf < 0)
|
||||
(stringfmt() << "Bad Opus packet").throwex();
|
||||
p.units = frames * samples_pf / 120;
|
||||
}
|
||||
expected_granule += p.units * 120;
|
||||
if(curpacket == pagepackets && curpage->get_eos()) {
|
||||
if(curpage->get_eos()) {
|
||||
//The postgap is expected_granule - granulepos.
|
||||
if(curpage->get_granulepos() > expected_granule)
|
||||
(stringfmt() << "Page granule too large, expected maximum of " <<
|
||||
expected_granule << ", got " << curpage->get_granulepos()).throwex();
|
||||
postgap = expected_granule - curpage->get_granulepos();
|
||||
if(postgap > p.units * 120) {
|
||||
messages << "Warning, postgap too large, clipped to last packet" << std::endl;
|
||||
postgap = p.units * 120;
|
||||
}
|
||||
} else if(!granule_correction_done)
|
||||
expected_granule = curpage->get_granulepos();
|
||||
else
|
||||
(stringfmt() << "Page granule invalid, expected " <<
|
||||
expected_granule << ", got " << curpage->get_granulepos()).throwex();
|
||||
granule_correction_done = true;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
size_t oggopus_importer::get_postgap()
|
||||
{
|
||||
return postgap;
|
||||
}
|
||||
|
||||
void opus_stream::import_stream_oggopus(std::ifstream& data)
|
||||
{
|
||||
ogg_stream_reader_iostreams reader(data);
|
||||
ogg_page page;
|
||||
oggopus_importer importer;
|
||||
uint32_t stream_seq = 0; //The imprinting duckling model.
|
||||
int state = 0; //Not locked.
|
||||
size_t advance;
|
||||
uint32_t last_page_seen = 0xFFFFFFFFUL;
|
||||
bool seen_data = false;
|
||||
reader.set_errors_to(messages);
|
||||
try {
|
||||
while(reader.get_page(page)) {
|
||||
if(state && page.get_stream() != stream_seq)
|
||||
continue; //Wrong stream.
|
||||
if(state && page.get_sequence() != last_page_seen + 1)
|
||||
messages << "Warning: Packet(s) missing in OggOpus stream" << std::endl;
|
||||
last_page_seen = page.get_sequence();
|
||||
struct oggopus_header h;
|
||||
struct oggopus_tags t;
|
||||
size_t packets = page.get_packet_count();
|
||||
switch(state) {
|
||||
case 0: //Not locked.
|
||||
try {
|
||||
h = parse_oggopus_header(page);
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
if(h.streams != 1)
|
||||
throw std::runtime_error("Multistream OggOpus streams are not "
|
||||
"supported");
|
||||
state = 1; //Expecting comment.
|
||||
pregap_length = h.preskip;
|
||||
gain = h.gain;
|
||||
stream_seq = page.get_stream();
|
||||
last_page_seen = page.get_sequence();
|
||||
break;
|
||||
case 1: //Expecting comment.
|
||||
t = parse_oggopus_tags(page);
|
||||
state = 2; //Data page.
|
||||
if(page.get_eos())
|
||||
throw std::runtime_error("Empty OggOpus stream");
|
||||
//We don't do anything with this.
|
||||
break;
|
||||
case 2: //Data page.
|
||||
importer.put_page(page);
|
||||
while(importer.packet_pending()) {
|
||||
auto p = importer.get_packet();
|
||||
if(p.units)
|
||||
write(p.units, p.data, p.size);
|
||||
}
|
||||
if(page.get_eos()) {
|
||||
state = 3; //End of stream.
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
out:
|
||||
if(state == 0)
|
||||
throw std::runtime_error("No OggOpus stream found");
|
||||
if(state == 1)
|
||||
throw std::runtime_error("Oggopus stream missing required tags page");
|
||||
if(state == 2)
|
||||
messages << "Warning: Incomplete Oggopus stream." << std::endl;
|
||||
postgap_length = importer.get_postgap();
|
||||
write_trailier();
|
||||
} catch(...) {
|
||||
if(ctrl_cluster) fs.free_cluster_chain(ctrl_cluster);
|
||||
if(data_cluster) fs.free_cluster_chain(data_cluster);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void opus_stream::import_stream_sox(std::ifstream& data)
|
||||
{
|
||||
int err;
|
||||
unsigned char tmpi[65536];
|
||||
float tmp[OPUS_MAX_OUT];
|
||||
char header[260];
|
||||
data.read(header, 32);
|
||||
if(!data)
|
||||
|
@ -341,11 +660,21 @@ out_parsing:
|
|||
uint64_t samples = read64ule(header + 8);
|
||||
OpusEncoder* enc = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &err);
|
||||
opus_encoder_ctl(enc, OPUS_SET_BITRATE(OPUS_BITRATE));
|
||||
for(uint64_t i = 0; i < samples; i += OPUS_BLOCK_SIZE) {
|
||||
int pregap;
|
||||
opus_encoder_ctl(enc, OPUS_GET_LOOKAHEAD(&pregap));
|
||||
pregap_length = pregap;
|
||||
for(uint64_t i = 0; i < samples + pregap; i += OPUS_BLOCK_SIZE) {
|
||||
size_t bs = OPUS_BLOCK_SIZE;
|
||||
if(i + bs > samples)
|
||||
bs = samples - i;
|
||||
data.read(reinterpret_cast<char*>(tmpi), 4 * bs);
|
||||
if(i + bs > samples + pregap)
|
||||
bs = samples + pregap - i;
|
||||
//We have to read zero bytes after the end of stream.
|
||||
size_t readable = bs;
|
||||
if(readable + i > samples)
|
||||
readable = max(samples, i) - i;
|
||||
if(readable > 0)
|
||||
data.read(reinterpret_cast<char*>(tmpi), 4 * readable);
|
||||
if(readable < bs)
|
||||
memset(tmpi + 4 * readable, 0, 4 * (bs - readable));
|
||||
if(!data) {
|
||||
if(ctrl_cluster) fs.free_cluster_chain(ctrl_cluster);
|
||||
if(data_cluster) fs.free_cluster_chain(data_cluster);
|
||||
|
@ -354,6 +683,8 @@ out_parsing:
|
|||
}
|
||||
for(size_t j = 0; j < bs; j++)
|
||||
tmp[j] = static_cast<float>(read32sle(tmpi + 4 * j)) / 268435456;
|
||||
if(bs < OPUS_BLOCK_SIZE)
|
||||
postgap_length = OPUS_BLOCK_SIZE - bs;
|
||||
for(size_t j = bs; j < OPUS_BLOCK_SIZE; j++)
|
||||
tmp[j] = 0;
|
||||
int r = opus_encode_float(enc, tmp, OPUS_BLOCK_SIZE, tmpi, sizeof(tmpi));
|
||||
|
@ -373,6 +704,12 @@ out_parsing:
|
|||
}
|
||||
}
|
||||
opus_encoder_destroy(enc);
|
||||
try {
|
||||
write_trailier();
|
||||
} catch(...) {
|
||||
if(ctrl_cluster) fs.free_cluster_chain(ctrl_cluster);
|
||||
if(data_cluster) fs.free_cluster_chain(data_cluster);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,13 +732,12 @@ out_parsing:
|
|||
delete this;
|
||||
}
|
||||
|
||||
void opus_stream::export_stream(std::ofstream& data, bool compressed)
|
||||
void opus_stream::export_stream_opusdemo(std::ofstream& data)
|
||||
{
|
||||
int err;
|
||||
OpusDecoder* dec = opus_decoder_create(48000, 1, &err);
|
||||
std::vector<unsigned char> p;
|
||||
float tmp[OPUS_MAX_OUT];
|
||||
if(compressed) {
|
||||
for(size_t i = 0; i < packets.size(); i++) {
|
||||
char head[8];
|
||||
unsigned state;
|
||||
|
@ -426,17 +762,85 @@ out_parsing:
|
|||
throw std::runtime_error("Error writing opus packet");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
opus_decoder_destroy(dec);
|
||||
}
|
||||
|
||||
void opus_stream::export_stream_oggopus(std::ofstream& data)
|
||||
{
|
||||
oggopus_header header;
|
||||
oggopus_tags tags;
|
||||
ogg_stream_writer_iostreams writer(data);
|
||||
//Headers / Tags.
|
||||
header.version = 1;
|
||||
header.channels = 1;
|
||||
header.preskip = pregap_length;
|
||||
header.rate = OPUS_SAMPLERATE;
|
||||
header.gain = 0;
|
||||
header.map_family = 0;
|
||||
header.streams = 1;
|
||||
header.coupled = 0;
|
||||
header.chanmap[0] = 0;
|
||||
memset(header.chanmap + 1, 255, 254);
|
||||
tags.vendor = "lsnes rr" + lsnes_version;
|
||||
tags.comments.push_back((stringfmt() << "LSNES_STREAM_TS=" << s_timebase).str());
|
||||
struct ogg_page hpage = serialize_oggopus_header(header);
|
||||
struct ogg_page tpage = serialize_oggopus_tags(tags);
|
||||
struct ogg_page ppage;
|
||||
uint64_t true_granule = 0;
|
||||
uint32_t seq = 2;
|
||||
//Empty stream?
|
||||
if(!packets.size())
|
||||
tpage.set_eos(true);
|
||||
writer.put_page(hpage);
|
||||
writer.put_page(tpage);
|
||||
for(size_t i = 0; i < packets.size(); i++) {
|
||||
std::vector<unsigned char> p;
|
||||
try {
|
||||
p = packet(i);
|
||||
} catch(std::exception& e) {
|
||||
(stringfmt() << "Error reading opus packet: " << e.what()).throwex();
|
||||
}
|
||||
if(!p.size())
|
||||
(stringfmt() << "Empty Opus packet is not valid").throwex();
|
||||
int frames = opus_packet_get_nb_frames(&p[0], p.size());
|
||||
int samples_pf = opus_packet_get_samples_per_frame(&p[0], OGGOPUS_GRANULERATE);
|
||||
if(frames < 0 || samples_pf < 0)
|
||||
(stringfmt() << "Bad Opus packet").throwex();
|
||||
if(!ppage.append_packet(&p[0], p.size())) {
|
||||
//Won't fit.
|
||||
ppage.set_granulepos(true_granule);
|
||||
ppage.set_sequence(seq++);
|
||||
writer.put_page(ppage);
|
||||
ppage = ogg_page(); //Reset.
|
||||
if(!ppage.append_packet(&p[0], p.size()))
|
||||
throw std::runtime_error("Internal error: Opus packet larger than page");
|
||||
}
|
||||
true_granule += frames * samples_pf;
|
||||
}
|
||||
ppage.set_eos(true);
|
||||
ppage.set_sequence(seq++);
|
||||
ppage.set_granulepos(true_granule - postgap_length);
|
||||
writer.put_page(ppage);
|
||||
}
|
||||
|
||||
void opus_stream::export_stream_sox(std::ofstream& data)
|
||||
{
|
||||
int err;
|
||||
OpusDecoder* dec = opus_decoder_create(48000, 1, &err);
|
||||
std::vector<unsigned char> p;
|
||||
float tmp[OPUS_MAX_OUT];
|
||||
char header[32];
|
||||
write64ule(header, 0x1C586F532EULL); //Magic and header size.
|
||||
write64ule(header + 16, 4676829883349860352ULL); //Sampling rate.
|
||||
write32ule(header + 24, 1);
|
||||
uint64_t tlen = 0;
|
||||
uint32_t lookahead_thrown = 0;
|
||||
data.write(header, 32);
|
||||
if(!data) {
|
||||
opus_decoder_destroy(dec);
|
||||
throw std::runtime_error("Error writing PCM data.");
|
||||
}
|
||||
float lgain = get_gain_linear();
|
||||
for(size_t i = 0; i < packets.size(); i++) {
|
||||
char blank[4] = {0, 0, 0, 0};
|
||||
std::vector<unsigned char> p;
|
||||
|
@ -448,20 +852,30 @@ out_parsing:
|
|||
}
|
||||
uint32_t len = packet_length(i);
|
||||
int r = opus_decode_float(dec, &p[0], p.size(), tmp, OPUS_MAX_OUT, 0);
|
||||
tlen += len;
|
||||
bool is_last = (i == packets.size() - 1);
|
||||
uint32_t pregap_throw = 0;
|
||||
uint32_t postgap_throw = 0;
|
||||
if(r < 0) {
|
||||
opus_decoder_destroy(dec);
|
||||
(stringfmt() << "Error decoding opus packet: " << opus_strerror(r)).throwex();
|
||||
} else {
|
||||
for(uint32_t j = 0; j < len; j++) {
|
||||
int32_t s = (int32_t)(tmp[j] * 268435456.0);
|
||||
}
|
||||
if(lookahead_thrown < pregap_length) {
|
||||
//We haven't yet thrown the full pregap. Throw some.
|
||||
uint32_t maxthrow = pregap_length - lookahead_thrown;
|
||||
pregap_throw = min(len, maxthrow);
|
||||
lookahead_thrown += pregap_length;
|
||||
}
|
||||
if(is_last)
|
||||
postgap_throw = min(len - pregap_throw, postgap_length);
|
||||
tlen += (len - pregap_throw - postgap_throw);
|
||||
for(uint32_t j = pregap_throw; j < len - postgap_throw; j++) {
|
||||
int32_t s = (int32_t)(tmp[j] * lgain * 268435456.0);
|
||||
write32sle(blank, s);
|
||||
data.write(blank, 4);
|
||||
if(!data)
|
||||
throw std::runtime_error("Error writing PCM data.");
|
||||
}
|
||||
}
|
||||
}
|
||||
data.seekp(0, std::ios_base::beg);
|
||||
write64ule(header + 8, tlen);
|
||||
data.write(header, 32);
|
||||
|
@ -469,10 +883,19 @@ out_parsing:
|
|||
opus_decoder_destroy(dec);
|
||||
throw std::runtime_error("Error writing PCM data.");
|
||||
}
|
||||
}
|
||||
opus_decoder_destroy(dec);
|
||||
}
|
||||
|
||||
void opus_stream::export_stream(std::ofstream& data, external_stream_format extfmt)
|
||||
{
|
||||
if(extfmt == EXTFMT_OPUSDEMO)
|
||||
export_stream_opusdemo(data);
|
||||
else if(extfmt == EXTFMT_OGGOPUS)
|
||||
export_stream_oggopus(data);
|
||||
else if(extfmt == EXTFMT_SOX)
|
||||
export_stream_sox(data);
|
||||
}
|
||||
|
||||
void opus_stream::write(uint8_t len, const unsigned char* payload, size_t payload_len)
|
||||
{
|
||||
try {
|
||||
|
@ -497,6 +920,29 @@ out_parsing:
|
|||
}
|
||||
}
|
||||
|
||||
void opus_stream::write_trailier()
|
||||
{
|
||||
try {
|
||||
char descriptor[16];
|
||||
uint32_t used_mcluster, used_moffset;
|
||||
//The allocation must be done for real.
|
||||
if(!next_mcluster)
|
||||
next_mcluster = ctrl_cluster = fs.allocate_cluster();
|
||||
//But the write must not update the pointers..
|
||||
uint32_t tmp_mcluster = next_mcluster;
|
||||
uint32_t tmp_moffset = next_moffset;
|
||||
write32ube(descriptor, 0);
|
||||
write32ube(descriptor + 4, (pregap_length << 8) | 0x02);
|
||||
write32ube(descriptor + 8, (postgap_length << 8) | 0x03);
|
||||
write16sbe(descriptor + 12, gain);
|
||||
write16ube(descriptor + 14, 0x0004);
|
||||
fs.write_data(tmp_mcluster, tmp_moffset, descriptor, 16, used_mcluster, used_moffset);
|
||||
} catch(std::exception& e) {
|
||||
(stringfmt() << "Can't write stream trailer: " << e.what()).throwex();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//Playing opus stream.
|
||||
struct opus_playback_stream
|
||||
|
@ -520,6 +966,8 @@ out_parsing:
|
|||
void decode_block();
|
||||
float output[OPUS_MAX_OUT];
|
||||
unsigned output_left;
|
||||
uint32_t pregap_thrown;
|
||||
bool postgap_thrown = false;
|
||||
OpusDecoder* decoder;
|
||||
opus_stream& stream;
|
||||
uint32_t next_block;
|
||||
|
@ -534,6 +982,8 @@ out_parsing:
|
|||
stream.lock();
|
||||
next_block = 0;
|
||||
output_left = 0;
|
||||
pregap_thrown = 0;
|
||||
postgap_thrown = false;
|
||||
blocks = stream.blocks();
|
||||
decoder = opus_decoder_create(OPUS_SAMPLERATE, 1, &err);
|
||||
if(!decoder)
|
||||
|
@ -571,13 +1021,28 @@ out_parsing:
|
|||
for(unsigned i = 0; i < plen; i++)
|
||||
output[output_left++] = 0;
|
||||
}
|
||||
//Throw the pregap away if needed.
|
||||
if(pregap_thrown < stream.get_pregap()) {
|
||||
uint32_t throw_amt = min(stream.get_pregap() - pregap_thrown, (uint32_t)output_left);
|
||||
if(throw_amt && throw_amt < output_left)
|
||||
memmove(output, output + throw_amt, (output_left - throw_amt) * sizeof(float));
|
||||
output_left -= throw_amt;
|
||||
pregap_thrown += throw_amt;
|
||||
}
|
||||
next_block++;
|
||||
}
|
||||
|
||||
void opus_playback_stream::read(float* data, size_t samples)
|
||||
{
|
||||
float lgain = stream.get_gain_linear();
|
||||
while(samples > 0) {
|
||||
decode_block();
|
||||
if(next_block >= blocks && !postgap_thrown) {
|
||||
//This is the final packet. Throw away postgap samples at the end.
|
||||
uint32_t thrown = min(stream.get_postgap(), (uint32_t)output_left);
|
||||
output_left -= thrown;
|
||||
postgap_thrown = true;
|
||||
}
|
||||
if(next_block >= blocks && !output_left) {
|
||||
//Zerofill remainder.
|
||||
for(size_t i = 0; i < samples; i++)
|
||||
|
@ -585,8 +1050,12 @@ out_parsing:
|
|||
return;
|
||||
}
|
||||
unsigned maxcopy = min(static_cast<unsigned>(samples), output_left);
|
||||
if(maxcopy) {
|
||||
memcpy(data, output, maxcopy * sizeof(float));
|
||||
if(maxcopy < output_left)
|
||||
for(size_t i = 0; i < maxcopy; i++)
|
||||
data[i] *= lgain;
|
||||
}
|
||||
if(maxcopy < output_left && maxcopy)
|
||||
memmove(output, output + maxcopy, (output_left - maxcopy) * sizeof(float));
|
||||
output_left -= maxcopy;
|
||||
samples -= maxcopy;
|
||||
|
@ -596,6 +1065,10 @@ out_parsing:
|
|||
|
||||
void opus_playback_stream::skip(uint64_t samples)
|
||||
{
|
||||
//Adjust for preskip and declare all preskip already thrown away.
|
||||
pregap_thrown = stream.get_pregap();
|
||||
samples += pregap_thrown;
|
||||
postgap_thrown = false;
|
||||
//First, skip inside decoded samples.
|
||||
if(samples < output_left) {
|
||||
//Skipping less than amount in output buffer. Just discard from output buffer and try
|
||||
|
@ -1171,6 +1644,9 @@ out_parsing:
|
|||
active_stream = NULL;
|
||||
try {
|
||||
active_stream = new opus_stream(ctime, current_collection->get_filesystem());
|
||||
int pregap;
|
||||
opus_encoder_ctl(e, OPUS_GET_LOOKAHEAD(&pregap));
|
||||
active_stream->set_pregap(pregap);
|
||||
} catch(std::exception& e) {
|
||||
messages << "Can't start stream: " << e.what() << std::endl;
|
||||
return;
|
||||
|
@ -1185,6 +1661,7 @@ out_parsing:
|
|||
messages << "Tangent negative edge. "
|
||||
<< total_compressed << " bytes in " << total_blocks << " blocks, "
|
||||
<< (0.4 * total_compressed / total_blocks) << " kbps" << std::endl;
|
||||
active_stream->write_trailier();
|
||||
if(current_collection) {
|
||||
try {
|
||||
current_collection->add_stream(*active_stream);
|
||||
|
@ -1205,6 +1682,13 @@ out_parsing:
|
|||
rptr = 0;
|
||||
fire();
|
||||
}
|
||||
void kill()
|
||||
{
|
||||
quit = true;
|
||||
while(!quit_ack)
|
||||
usleep(100000);
|
||||
usleep(100000);
|
||||
}
|
||||
protected:
|
||||
void entry()
|
||||
{
|
||||
|
@ -1215,6 +1699,7 @@ out_parsing:
|
|||
} catch(std::exception& e) {
|
||||
messages << "AIEEE... Fatal exception in voice thread: " << e.what() << std::endl;
|
||||
}
|
||||
quit_ack = true;
|
||||
}
|
||||
void entry2()
|
||||
{
|
||||
|
@ -1255,8 +1740,10 @@ out_parsing:
|
|||
handle_tangent_positive_edge(oenc, active_stream, total_compressed,
|
||||
total_blocks);
|
||||
}
|
||||
else if(!active_flag && active_stream)
|
||||
else if((!active_flag || quit) && active_stream)
|
||||
handle_tangent_negative_edge(active_stream, total_compressed, total_blocks);
|
||||
if(quit)
|
||||
break;
|
||||
|
||||
//Read input, up to 25ms.
|
||||
unsigned rate = audioapi_voice_rate();
|
||||
|
@ -1300,6 +1787,8 @@ out_parsing:
|
|||
private:
|
||||
size_t rptr;
|
||||
double position;
|
||||
volatile bool quit;
|
||||
volatile bool quit_ack;
|
||||
};
|
||||
|
||||
//The tangent function.
|
||||
|
@ -1313,9 +1802,8 @@ out_parsing:
|
|||
[]() throw(std::bad_alloc, std::runtime_error) {
|
||||
active_flag = false;
|
||||
});
|
||||
inthread_th* int_task;
|
||||
|
||||
|
||||
inthread_th* task = NULL;
|
||||
}
|
||||
|
||||
void voice_frame_number(uint64_t newframe, double rate)
|
||||
|
@ -1332,12 +1820,13 @@ void voice_frame_number(uint64_t newframe, double rate)
|
|||
|
||||
void voicethread_task()
|
||||
{
|
||||
task = new inthread_th;
|
||||
int_task = new inthread_th;
|
||||
}
|
||||
|
||||
void voicethread_task_end()
|
||||
void voicethread_kill()
|
||||
{
|
||||
delete task;
|
||||
int_task->kill();
|
||||
int_task = NULL;
|
||||
}
|
||||
|
||||
uint64_t voicesub_parse_timebase(const std::string& n)
|
||||
|
@ -1442,7 +1931,7 @@ namespace
|
|||
<< std::endl;
|
||||
});
|
||||
|
||||
void import_cmd_common(const std::string& x, const char* postfix, bool mode)
|
||||
void import_cmd_common(const std::string& x, const char* postfix, external_stream_format mode)
|
||||
{
|
||||
umutex_class m2(current_collection_lock);
|
||||
if(!current_collection) {
|
||||
|
@ -1477,17 +1966,24 @@ namespace
|
|||
"stream", "import-stream-opus <timebase> <filename>\nImport opus stream from <filename>, starting at "
|
||||
"<timebase>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
import_cmd_common(x, "opus", true);
|
||||
import_cmd_common(x, "opus", EXTFMT_OPUSDEMO);
|
||||
});
|
||||
|
||||
function_ptr_command<const std::string&> import_stream_p(lsnes_cmd, "import-stream-pcm", "Import a PCM "
|
||||
"stream", "import-stream-pcm <timebase> <filename>\nImport PCM stream from <filename>, starting at "
|
||||
"<timebase>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
import_cmd_common(x, "pcm", false);
|
||||
import_cmd_common(x, "pcm", EXTFMT_SOX);
|
||||
});
|
||||
|
||||
void export_cmd_common(const std::string& x, const char* postfix, bool mode)
|
||||
function_ptr_command<const std::string&> import_stream_o(lsnes_cmd, "import-stream-ogg",
|
||||
"Import a OggOpus stream", "import-stream-ogg <timebase> <filename>\nImport OggOpus stream from "
|
||||
" <filename>, starting at <timebase>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
import_cmd_common(x, "ogg", EXTFMT_OGGOPUS);
|
||||
});
|
||||
|
||||
void export_cmd_common(const std::string& x, const char* postfix, external_stream_format mode)
|
||||
{
|
||||
umutex_class m2(current_collection_lock);
|
||||
if(!current_collection) {
|
||||
|
@ -1523,13 +2019,20 @@ namespace
|
|||
function_ptr_command<const std::string&> export_stream_c(lsnes_cmd, "export-stream-opus", "Export a opus "
|
||||
"stream", "export-stream-opus <id> <filename>\nExport opus stream <id> to <filename>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
export_cmd_common(x, "opus", true);
|
||||
export_cmd_common(x, "opus", EXTFMT_OPUSDEMO);
|
||||
});
|
||||
|
||||
function_ptr_command<const std::string&> export_stream_p(lsnes_cmd, "export-stream-pcm",
|
||||
"Export a PCM stream", "export-stream-pcm <id> <filename>\nExport PCM stream <id> to <filename>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
export_cmd_common(x, "pcm", false);
|
||||
export_cmd_common(x, "pcm", EXTFMT_SOX);
|
||||
});
|
||||
|
||||
function_ptr_command<const std::string&> export_stream_o(lsnes_cmd, "export-stream-ogg",
|
||||
"Export a OggOpus stream", "export-stream-ogg <id> <filename>\nExport OggOpus stream <id> to "
|
||||
"<filename>",
|
||||
[](const std::string& x) throw(std::bad_alloc, std::runtime_error) {
|
||||
export_cmd_common(x, "ogg", EXTFMT_OGGOPUS);
|
||||
});
|
||||
|
||||
function_ptr_command<const std::string&> export_sstream(lsnes_cmd, "export-superstream", "Export superstream",
|
||||
|
@ -1626,7 +2129,7 @@ void voicesub_play_stream(uint64_t id)
|
|||
s->put_ref();
|
||||
}
|
||||
|
||||
void voicesub_export_stream(uint64_t id, const std::string& filename, bool opus)
|
||||
void voicesub_export_stream(uint64_t id, const std::string& filename, external_stream_format fmt)
|
||||
{
|
||||
umutex_class m2(current_collection_lock);
|
||||
if(!current_collection)
|
||||
|
@ -1640,7 +2143,7 @@ void voicesub_export_stream(uint64_t id, const std::string& filename, bool opus)
|
|||
throw std::runtime_error("Can't open output file");
|
||||
}
|
||||
try {
|
||||
st->export_stream(s, opus);
|
||||
st->export_stream(s, fmt);
|
||||
} catch(std::exception& e) {
|
||||
st->put_ref();
|
||||
(stringfmt() << "Export failed: " << e.what()).throwex();
|
||||
|
@ -1648,7 +2151,7 @@ void voicesub_export_stream(uint64_t id, const std::string& filename, bool opus)
|
|||
st->put_ref();
|
||||
}
|
||||
|
||||
uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, bool opus)
|
||||
uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, external_stream_format fmt)
|
||||
{
|
||||
umutex_class m2(current_collection_lock);
|
||||
if(!current_collection)
|
||||
|
@ -1657,7 +2160,7 @@ uint64_t voicesub_import_stream(uint64_t ts, const std::string& filename, bool o
|
|||
std::ifstream s(filename, std::ios_base::in | std::ios_base::binary);
|
||||
if(!s)
|
||||
throw std::runtime_error("Can't open input file");
|
||||
opus_stream* st = new opus_stream(ts, current_collection->get_filesystem(), s, opus);
|
||||
opus_stream* st = new opus_stream(ts, current_collection->get_filesystem(), s, fmt);
|
||||
uint64_t id;
|
||||
try {
|
||||
id = current_collection->add_stream(*st);
|
||||
|
@ -1721,16 +2224,7 @@ double voicesub_ts_seconds(uint64_t ts)
|
|||
return ts / 48000.0;
|
||||
}
|
||||
#else
|
||||
void voicethread_task()
|
||||
{
|
||||
}
|
||||
|
||||
void voicethread_task_end()
|
||||
{
|
||||
}
|
||||
|
||||
void voice_frame_number(uint64_t newframe, double rate)
|
||||
{
|
||||
}
|
||||
|
||||
void voicethread_task() {}
|
||||
void voice_frame_number(uint64_t newframe, double rate) {}
|
||||
void voicethread_kill() {}
|
||||
#endif
|
||||
|
|
|
@ -960,6 +960,6 @@ void main_loop(struct loaded_rom& rom, struct moviefile& initial, bool load_has_
|
|||
lua_callback_do_frame();
|
||||
}
|
||||
information_dispatch::do_dump_end();
|
||||
voicethread_task_end();
|
||||
core_uninstall_handler();
|
||||
voicethread_kill();
|
||||
}
|
||||
|
|
570
src/library/ogg.cpp
Normal file
570
src/library/ogg.cpp
Normal file
|
@ -0,0 +1,570 @@
|
|||
#include "ogg.hpp"
|
||||
#include "serialization.hpp"
|
||||
#include <cstring>
|
||||
#include <zlib.h>
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
const uint32_t crc_lookup[256]= {
|
||||
0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9,
|
||||
0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005,
|
||||
0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61,
|
||||
0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
|
||||
0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9,
|
||||
0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
|
||||
0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011,
|
||||
0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
|
||||
0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039,
|
||||
0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
|
||||
0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81,
|
||||
0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
|
||||
0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49,
|
||||
0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
|
||||
0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1,
|
||||
0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
|
||||
0x34867077,0x30476dc0,0x3d044b19,0x39c556ae,
|
||||
0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
|
||||
0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16,
|
||||
0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
|
||||
0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde,
|
||||
0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
|
||||
0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066,
|
||||
0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
|
||||
0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e,
|
||||
0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
|
||||
0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6,
|
||||
0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
|
||||
0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e,
|
||||
0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
|
||||
0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686,
|
||||
0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
|
||||
0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637,
|
||||
0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
|
||||
0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f,
|
||||
0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
|
||||
0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47,
|
||||
0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
|
||||
0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff,
|
||||
0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
|
||||
0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7,
|
||||
0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
|
||||
0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f,
|
||||
0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
|
||||
0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7,
|
||||
0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
|
||||
0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f,
|
||||
0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
|
||||
0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640,
|
||||
0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
|
||||
0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8,
|
||||
0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
|
||||
0x119b4be9,0x155a565e,0x18197087,0x1cd86d30,
|
||||
0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
|
||||
0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088,
|
||||
0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
|
||||
0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0,
|
||||
0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
|
||||
0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18,
|
||||
0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
|
||||
0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0,
|
||||
0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
|
||||
0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668,
|
||||
0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
|
||||
};
|
||||
|
||||
uint32_t ogg_crc32(uint32_t chain, const uint8_t* data, size_t size)
|
||||
{
|
||||
if(!data)
|
||||
return 0;
|
||||
for(size_t i = 0; i < size; i++)
|
||||
chain = (chain << 8) ^ crc_lookup[(chain >> 24) ^ data[i]];
|
||||
return chain;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ogg_page::ogg_page() throw()
|
||||
{
|
||||
version = 0;
|
||||
flag_continue = false;
|
||||
flag_bos = false;
|
||||
flag_eos = false;
|
||||
last_incomplete = false;
|
||||
granulepos = granulepos_none;
|
||||
stream = 0;
|
||||
sequence = 0;
|
||||
segment_count = 0;
|
||||
packet_count = 0;
|
||||
data_count = 0;
|
||||
memset(data, 0, sizeof(data));
|
||||
memset(segments, 0, sizeof(segments));
|
||||
memset(packets, 0, sizeof(packets));
|
||||
}
|
||||
|
||||
ogg_page::ogg_page(const char* buffer, size_t& advance) throw(std::runtime_error)
|
||||
{
|
||||
//Check validity of page header.
|
||||
if(buffer[0] != 'O' || buffer[1] != 'g' || buffer[2] != 'g' || buffer[3] != 'S')
|
||||
throw std::runtime_error("Bad Ogg page header");
|
||||
if(buffer[4] != 0)
|
||||
throw std::runtime_error("Bad Ogg page version");
|
||||
if(buffer[5] & 0xF8)
|
||||
throw std::runtime_error("Bad Ogg page flags");
|
||||
//Compute length.
|
||||
size_t b = 27 + (unsigned char)buffer[26];
|
||||
for(unsigned i = 0; i < (unsigned char)buffer[26]; i++)
|
||||
b += (unsigned char)buffer[27 + i];
|
||||
//Check the CRC.
|
||||
uint32_t claimed = read32ule(buffer + 22);
|
||||
uint32_t x = 0;
|
||||
uint32_t actual = ogg_crc32(0, NULL, 0);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(buffer), 22);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(&x), 4);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(buffer + 26), b - 26);
|
||||
if(claimed != actual)
|
||||
throw std::runtime_error("Bad Ogg page checksum");
|
||||
//This packet is valid.
|
||||
version = buffer[4];
|
||||
uint8_t flags = buffer[5];
|
||||
flag_continue = (flags & 1);
|
||||
flag_bos = (flags & 2);
|
||||
flag_eos = (flags & 4);
|
||||
granulepos = read64ule(buffer + 6);
|
||||
stream = read32ule(buffer + 14);
|
||||
sequence = read32ule(buffer + 18);
|
||||
segment_count = buffer[26];
|
||||
memset(segments, 0, sizeof(segments));
|
||||
if(segment_count)
|
||||
memcpy(segments, buffer + 27, segment_count);
|
||||
memset(data, 0, sizeof(data));
|
||||
if(b > 27 + segment_count)
|
||||
memcpy(data, buffer + 27 + segment_count, b - 27 - segment_count);
|
||||
packet_count = 0;
|
||||
memset(packets, 0, sizeof(packets));
|
||||
if(segment_count > 0)
|
||||
packets[packet_count++] = 0;
|
||||
uint16_t dptr = 0;
|
||||
for(unsigned i = 0; i < segment_count; i++) {
|
||||
dptr += segments[i];
|
||||
if(segment_count > i + 1 && segments[i] < 255)
|
||||
packets[packet_count++] = dptr;
|
||||
}
|
||||
packets[packet_count] = dptr;
|
||||
last_incomplete = (!flag_eos && segment_count > 0 && segments[segment_count - 1] == 255);
|
||||
advance = b;
|
||||
}
|
||||
|
||||
bool ogg_page::scan(const char* buffer, size_t bufferlen, bool eof, size_t& advance) throw()
|
||||
{
|
||||
const char* _buffer = buffer;
|
||||
size_t buffer_left = bufferlen;
|
||||
int capture_state = 0;
|
||||
advance = 0;
|
||||
while(buffer_left >= 27) {
|
||||
//Check capture pattern.
|
||||
if(_buffer[0] != 'O' || _buffer[1] != 'g' || _buffer[2] != 'g' || _buffer[3] != 'S') {
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
//Check that version is valid.
|
||||
if(_buffer[4] != 0) {
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
//Check that flags are valid.
|
||||
if(_buffer[5] & 0xF8) {
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
//Check that segment table is present. If not, more data can uncover a page here.
|
||||
if(27 + (unsigned char)_buffer[26] > buffer_left) {
|
||||
if(!eof) {
|
||||
return false;
|
||||
} else {
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Check that all data is there. If not, more data can uncover a page here.
|
||||
size_t b = 27 + (unsigned char)_buffer[26];
|
||||
for(unsigned i = 0; i < (unsigned char)_buffer[26]; i++)
|
||||
b += (unsigned char)_buffer[27 + i];
|
||||
if(b > buffer_left) {
|
||||
if(!eof) {
|
||||
return false;
|
||||
} else {
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//Check the CRC.
|
||||
uint32_t claimed = read32ule(_buffer + 22);
|
||||
uint32_t x = 0;
|
||||
uint32_t actual = ogg_crc32(0, NULL, 0);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(_buffer), 22);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(&x), 4);
|
||||
actual = ogg_crc32(actual, reinterpret_cast<const uint8_t*>(_buffer + 26), b - 26);
|
||||
if(claimed != actual) {
|
||||
//CRC check fails. Advance.
|
||||
advance++;
|
||||
_buffer++;
|
||||
buffer_left--;
|
||||
continue;
|
||||
}
|
||||
return true; //Here is a packet.
|
||||
}
|
||||
if(eof && buffer_left < 27) {
|
||||
//Advance to the end.
|
||||
advance += buffer_left;
|
||||
}
|
||||
}
|
||||
|
||||
bool ogg_page::append_packet(const uint8_t* _data, size_t datalen) throw()
|
||||
{
|
||||
//Compute the smallest amount of data we can't write.
|
||||
size_t imin = (255 - segment_count) * 255;
|
||||
if(datalen >= imin)
|
||||
return false; //Can't write.
|
||||
//Okay, it fits. Write.
|
||||
packets[packet_count++] = data_count;
|
||||
bool terminate = false;
|
||||
while(datalen > 0) {
|
||||
if(datalen >= 255) {
|
||||
segments[segment_count++] = 255;
|
||||
memcpy(data + data_count, _data, 255);
|
||||
data_count += 255;
|
||||
_data += 255;
|
||||
datalen -= 255;
|
||||
} else {
|
||||
segments[segment_count++] = datalen;
|
||||
memcpy(data + data_count, _data, datalen);
|
||||
data_count += datalen;
|
||||
_data += datalen;
|
||||
datalen = 0;
|
||||
terminate = true;
|
||||
}
|
||||
}
|
||||
if(!terminate)
|
||||
segments[segment_count++] = 0;
|
||||
packets[packet_count] = data_count;
|
||||
last_incomplete = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ogg_page::append_packet_incomplete(const uint8_t*& _data, size_t& datalen) throw()
|
||||
{
|
||||
//If we have absolutely no space, don't flag a packet.
|
||||
if(segment_count == 255)
|
||||
return false;
|
||||
packets[packet_count++] = data_count;
|
||||
//Append segments, one by one.
|
||||
while(segment_count < 255) {
|
||||
if(datalen >= 255) {
|
||||
segments[segment_count++] = 255;
|
||||
memcpy(data + data_count, _data, 255);
|
||||
data_count += 255;
|
||||
_data += 255;
|
||||
datalen -= 255;
|
||||
} else {
|
||||
//Final segment of packet.
|
||||
segments[segment_count++] = datalen;
|
||||
memcpy(data + data_count, _data, datalen);
|
||||
data_count += datalen;
|
||||
_data += datalen;
|
||||
datalen = 0;
|
||||
packets[packet_count] = data_count;
|
||||
last_incomplete = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
packets[packet_count] = data_count;
|
||||
last_incomplete = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void ogg_page::serialize(char* buffer) const throw()
|
||||
{
|
||||
memcpy(buffer, "OggS", 4);
|
||||
buffer[4] = version;
|
||||
buffer[5] = (flag_continue ? 1 : 0) | (flag_bos ? 2 : 0) | (flag_eos ? 4 : 0);
|
||||
write64ule(buffer + 6, granulepos);
|
||||
write32ule(buffer + 14, stream);
|
||||
write32ule(buffer + 18, sequence);
|
||||
write32ule(buffer + 22, 0); //CRC will be fixed later.
|
||||
buffer[26] = segment_count;
|
||||
memcpy(buffer + 27, segments, segment_count);
|
||||
memcpy(buffer + 27 + segment_count, data, data_count);
|
||||
size_t plen = 27 + segment_count + data_count;
|
||||
//Fix the CRC.
|
||||
write32ule(buffer + 22, ogg_crc32(ogg_crc32(0, NULL, 0), reinterpret_cast<uint8_t*>(buffer), plen));
|
||||
}
|
||||
|
||||
const uint64_t ogg_page::granulepos_none = 0xFFFFFFFFFFFFFFFFULL;
|
||||
|
||||
|
||||
struct oggopus_header parse_oggopus_header(struct ogg_page& page) throw(std::runtime_error)
|
||||
{
|
||||
struct oggopus_header h;
|
||||
if(page.get_packet_count() != 1 || page.get_last_packet_incomplete() || page.get_continue())
|
||||
throw std::runtime_error("OggOpus header page must have one complete packet");
|
||||
if(!page.get_bos() || page.get_eos())
|
||||
throw std::runtime_error("OggOpus header page must be first but not last page");
|
||||
auto p = page.get_packet(0);
|
||||
if(p.second < 8 || memcmp(p.first, "OpusHead", 8))
|
||||
throw std::runtime_error("Bad OggOpus header magic");
|
||||
if(p.second < 19 || (p.first[18] && p.second < 21 + p.first[5]))
|
||||
throw std::runtime_error("OggOpus header packet truncated");
|
||||
if(!p.first[9])
|
||||
throw std::runtime_error("Zero channels not allowed");
|
||||
if(p.first[8] & 0xF0)
|
||||
throw std::runtime_error("Unsupported OggOpus version");
|
||||
h.version = p.first[8];
|
||||
h.channels = p.first[9];
|
||||
h.preskip = read16ule(p.first + 10);
|
||||
h.rate = read32ule(p.first + 12);
|
||||
h.gain = read16sle(p.first + 16);
|
||||
h.map_family = p.first[18];
|
||||
memset(h.chanmap, 255, sizeof(h.chanmap));
|
||||
if(h.map_family) {
|
||||
h.streams = p.first[19];
|
||||
h.coupled = p.first[20];
|
||||
if(h.coupled > h.streams)
|
||||
throw std::runtime_error("More coupled streams than total streams.");
|
||||
if(static_cast<int>(h.streams) > 255 - h.coupled)
|
||||
throw std::runtime_error("Maximum of 255 physical channels exceeded");
|
||||
memcpy(h.chanmap, p.first + 21, h.channels);
|
||||
for(unsigned i = 0; i < h.channels; i++)
|
||||
if(h.chanmap[i] != 255 && h.chanmap[i] > h.streams + h.coupled)
|
||||
throw std::runtime_error("Logical channel mapped to invalid physical channel");
|
||||
} else {
|
||||
h.streams = 1;
|
||||
if(h.channels > 2)
|
||||
throw std::runtime_error("Only 1 or 2 channels allowed with mapping family 0");
|
||||
h.coupled = (h.channels == 2) ? 1 : 0;
|
||||
h.chanmap[0] = 0;
|
||||
if(h.channels == 2) h.chanmap[1] = 1;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
struct oggopus_tags parse_oggopus_tags(struct ogg_page& page) throw(std::bad_alloc, std::runtime_error)
|
||||
{
|
||||
struct oggopus_tags h;
|
||||
if(page.get_packet_count() != 1 || page.get_last_packet_incomplete() || page.get_continue())
|
||||
throw std::runtime_error("OggOpus tags page must have one complete packet");
|
||||
if(page.get_bos())
|
||||
throw std::runtime_error("OggOpus tags page must not be first page");
|
||||
auto p = page.get_packet(0);
|
||||
if(p.second < 8 || memcmp(p.first, "OpusTags", 8))
|
||||
throw std::runtime_error("Bad OggOpus tags magic");
|
||||
if(p.second < 12)
|
||||
throw std::runtime_error("OggOpus header packet truncated");
|
||||
//Scan the thing.
|
||||
size_t itr = 8;
|
||||
size_t oitr = 8;
|
||||
itr = itr + 4 + read32ule(p.first + itr);
|
||||
if(itr + 4 > p.second)
|
||||
throw std::runtime_error("OggOpus header packet truncated");
|
||||
h.vendor = std::string(p.first + oitr + 4, p.first + itr);
|
||||
oitr = itr;
|
||||
uint32_t headers = read32ule(p.first + itr);
|
||||
itr += 4;
|
||||
for(uint32_t i = 0; i < headers; i++) {
|
||||
if(itr + 4 > p.second)
|
||||
throw std::runtime_error("OggOpus header packet truncated");
|
||||
itr = itr + 4 + read32ule(p.first + itr);
|
||||
h.comments.push_back(std::string(p.first + oitr + 4, p.first + itr));
|
||||
oitr = itr;
|
||||
}
|
||||
if(itr > p.second)
|
||||
throw std::runtime_error("OggOpus header packet truncated");
|
||||
return h;
|
||||
}
|
||||
|
||||
struct ogg_page serialize_oggopus_header(struct oggopus_header& header) throw(std::runtime_error)
|
||||
{
|
||||
struct ogg_page page;
|
||||
unsigned char buffer[276];
|
||||
size_t bsize = 19;
|
||||
if(header.version != 1)
|
||||
throw std::runtime_error("Don't how to serialize this oggopus version");
|
||||
if(!header.channels || (header.channels > 2 && !header.map_family))
|
||||
throw std::runtime_error("Illegal channel count");
|
||||
if(header.map_family && static_cast<int>(header.streams) > 255 - header.coupled)
|
||||
throw std::runtime_error("Maximum of 255 physical channels exceeded");
|
||||
if(header.map_family)
|
||||
for(unsigned i = 0; i < header.channels; i++)
|
||||
if(header.chanmap[i] != 255 && header.chanmap[i] > header.streams + header.coupled)
|
||||
throw std::runtime_error("Logical channel mapped to invalid physical channel");
|
||||
write64ube(buffer, 0x4F70757348656164ULL);
|
||||
buffer[8] = header.version;
|
||||
buffer[9] = header.channels;
|
||||
write16ule(buffer + 10, header.preskip);
|
||||
write32ule(buffer + 12, header.rate);
|
||||
write16sle(buffer + 16, header.gain);
|
||||
buffer[18] = header.map_family;
|
||||
if(header.map_family) {
|
||||
buffer[19] = header.streams;
|
||||
buffer[20] = header.coupled;
|
||||
memcpy(buffer + 21, header.chanmap, header.channels);
|
||||
bsize = 21 + header.channels;
|
||||
} else
|
||||
bsize = 19;
|
||||
if(!page.append_packet(buffer, bsize))
|
||||
throw std::runtime_error("Header packet too large");
|
||||
page.set_granulepos(0);
|
||||
page.set_sequence(0);
|
||||
page.set_bos(true);
|
||||
return page;
|
||||
}
|
||||
|
||||
struct ogg_page serialize_oggopus_tags(struct oggopus_tags& tags) throw(std::runtime_error)
|
||||
{
|
||||
struct ogg_page page;
|
||||
size_t needed = 8;
|
||||
bool toolarge = false;
|
||||
toolarge |= (tags.vendor.length() > 65016);
|
||||
needed += tags.vendor.length();
|
||||
toolarge |= (tags.comments.size() > 16254);
|
||||
for(auto i : tags.comments) {
|
||||
toolarge |= (tags.comments.size() > 65016);
|
||||
needed += (i.length() + 4);
|
||||
}
|
||||
if(needed > 65016 || toolarge)
|
||||
throw std::runtime_error("Set of comments too large");
|
||||
uint8_t buffer[65024];
|
||||
size_t itr = 0;
|
||||
write64ube(buffer, 0x4F70757354616773ULL);
|
||||
write32ule(buffer + 8, tags.vendor.length());
|
||||
std::copy(tags.vendor.begin(), tags.vendor.end(), reinterpret_cast<char*>(buffer + 12));
|
||||
itr = 12 + tags.vendor.length();
|
||||
write32ule(buffer + itr, tags.comments.size());
|
||||
itr += 4;
|
||||
for(auto i : tags.comments) {
|
||||
write32ule(buffer + itr, i.length());
|
||||
std::copy(i.begin(), i.end(), reinterpret_cast<char*>(buffer + itr + 4));
|
||||
itr += (i.length() + 4);
|
||||
}
|
||||
if(!page.append_packet(buffer, itr))
|
||||
throw std::runtime_error("Comment packet too large");
|
||||
page.set_granulepos(0);
|
||||
page.set_sequence(1);
|
||||
return page;
|
||||
}
|
||||
|
||||
ogg_stream_reader::ogg_stream_reader() throw()
|
||||
{
|
||||
eof = false;
|
||||
left = 0;
|
||||
errors_to = &std::cerr;
|
||||
}
|
||||
|
||||
ogg_stream_reader::~ogg_stream_reader() throw()
|
||||
{
|
||||
}
|
||||
|
||||
void ogg_stream_reader::set_errors_to(std::ostream& os)
|
||||
{
|
||||
errors_to = &os;
|
||||
}
|
||||
|
||||
bool ogg_stream_reader::get_page(ogg_page& page) throw(std::exception)
|
||||
{
|
||||
size_t advance;
|
||||
bool f;
|
||||
try_again:
|
||||
fill_buffer();
|
||||
if(eof && !left)
|
||||
return false;
|
||||
f = ogg_page::scan(buffer, left, eof, advance);
|
||||
if(advance) {
|
||||
//The ogg stream resyncs.
|
||||
(*errors_to) << "Warning: Ogg stream: Recapture after " << advance << " bytes." << std::endl;
|
||||
discard_buffer(advance);
|
||||
goto try_again;
|
||||
}
|
||||
if(!f)
|
||||
goto try_again;
|
||||
page = ogg_page(buffer, advance);
|
||||
discard_buffer(advance);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ogg_stream_reader::fill_buffer()
|
||||
{
|
||||
size_t r;
|
||||
if(!eof && left < sizeof(buffer)) {
|
||||
left += (r = read(buffer + left, sizeof(buffer) - left));
|
||||
if(!r)
|
||||
eof = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ogg_stream_reader::discard_buffer(size_t amount)
|
||||
{
|
||||
if(amount < left)
|
||||
memmove(buffer, buffer + amount, left - amount);
|
||||
left -= amount;
|
||||
}
|
||||
|
||||
ogg_stream_writer::ogg_stream_writer() throw()
|
||||
{
|
||||
}
|
||||
|
||||
ogg_stream_writer::~ogg_stream_writer() throw()
|
||||
{
|
||||
}
|
||||
|
||||
void ogg_stream_writer::put_page(const ogg_page& page) throw(std::exception)
|
||||
{
|
||||
char buffer[65536];
|
||||
size_t s = page.serialize_size();
|
||||
page.serialize(buffer);
|
||||
write(buffer, s);
|
||||
}
|
||||
|
||||
ogg_stream_reader_iostreams::ogg_stream_reader_iostreams(std::istream& stream)
|
||||
: is(stream)
|
||||
{
|
||||
}
|
||||
|
||||
ogg_stream_reader_iostreams::~ogg_stream_reader_iostreams() throw()
|
||||
{
|
||||
}
|
||||
|
||||
size_t ogg_stream_reader_iostreams::read(char* buffer, size_t size) throw(std::exception)
|
||||
{
|
||||
if(!is)
|
||||
return 0;
|
||||
is.read(buffer, size);
|
||||
return is.gcount();
|
||||
}
|
||||
|
||||
ogg_stream_writer_iostreams::ogg_stream_writer_iostreams(std::ostream& stream)
|
||||
: os(stream)
|
||||
{
|
||||
}
|
||||
|
||||
ogg_stream_writer_iostreams::~ogg_stream_writer_iostreams() throw()
|
||||
{
|
||||
}
|
||||
|
||||
void ogg_stream_writer_iostreams::write(const char* buffer, size_t size) throw(std::exception)
|
||||
{
|
||||
if(!os)
|
||||
throw std::runtime_error("Error writing data");
|
||||
os.write(buffer, size);
|
||||
if(!os)
|
||||
throw std::runtime_error("Error writing data");
|
||||
}
|
|
@ -132,7 +132,7 @@ namespace
|
|||
|
||||
//Blacklist these devices for recording, portaudio is buggy with recording off
|
||||
//these things.
|
||||
bool buggy = /*(!strcmp(inf->name, "default") || !strcmp(inf->name, "sysdefault"));*/false;
|
||||
bool buggy = (!strcmp(inf->name, "default") || !strcmp(inf->name, "sysdefault"));
|
||||
|
||||
PaStreamParameters input;
|
||||
memset(&input, 0, sizeof(input));
|
||||
|
|
|
@ -28,9 +28,11 @@ public:
|
|||
void on_delete(wxCommandEvent& e);
|
||||
void on_export_o(wxCommandEvent& e);
|
||||
void on_export_p(wxCommandEvent& e);
|
||||
void on_export_q(wxCommandEvent& e);
|
||||
void on_export_s(wxCommandEvent& e);
|
||||
void on_import_o(wxCommandEvent& e);
|
||||
void on_import_p(wxCommandEvent& e);
|
||||
void on_import_q(wxCommandEvent& e);
|
||||
void on_change_ts(wxCommandEvent& e);
|
||||
void on_load(wxCommandEvent& e);
|
||||
void on_unload(wxCommandEvent& e);
|
||||
|
@ -47,9 +49,11 @@ private:
|
|||
wxButton* deletebutton;
|
||||
wxButton* exportobutton;
|
||||
wxButton* exportpbutton;
|
||||
wxButton* exportqbutton;
|
||||
wxButton* exportsbutton;
|
||||
wxButton* importobutton;
|
||||
wxButton* importpbutton;
|
||||
wxButton* importqbutton;
|
||||
wxButton* changetsbutton;
|
||||
wxButton* loadbutton;
|
||||
wxButton* unloadbutton;
|
||||
|
@ -78,6 +82,7 @@ wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent)
|
|||
pbutton_s = new wxBoxSizer(wxHORIZONTAL);
|
||||
pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("Export")), 0, wxGROW);
|
||||
pbutton_s->Add(exportobutton = new wxButton(this, wxID_ANY, wxT("Opus")), 0, wxGROW);
|
||||
pbutton_s->Add(exportqbutton = new wxButton(this, wxID_ANY, wxT("Ogg")), 0, wxGROW);
|
||||
pbutton_s->Add(exportpbutton = new wxButton(this, wxID_ANY, wxT("Sox")), 0, wxGROW);
|
||||
pbutton_s->Add(exportsbutton = new wxButton(this, wxID_ANY, wxT("Superstream")), 0, wxGROW);
|
||||
top_s->Add(pbutton_s, 1, wxGROW);
|
||||
|
@ -86,6 +91,7 @@ wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent)
|
|||
pbutton_s = new wxBoxSizer(wxHORIZONTAL);
|
||||
pbutton_s->Add(new wxStaticText(this, wxID_ANY, wxT("Import")), 0, wxGROW);
|
||||
pbutton_s->Add(importobutton = new wxButton(this, wxID_ANY, wxT("Opus")), 0, wxGROW);
|
||||
pbutton_s->Add(importqbutton = new wxButton(this, wxID_ANY, wxT("Ogg")), 0, wxGROW);
|
||||
pbutton_s->Add(importpbutton = new wxButton(this, wxID_ANY, wxT("Sox")), 0, wxGROW);
|
||||
top_s->Add(pbutton_s, 1, wxGROW);
|
||||
pbutton_s->SetSizeHints(this);
|
||||
|
@ -118,12 +124,16 @@ wxeditor_voicesub::wxeditor_voicesub(wxWindow* parent)
|
|||
wxCommandEventHandler(wxeditor_voicesub::on_export_o), NULL, this);
|
||||
exportpbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_export_p), NULL, this);
|
||||
exportqbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_export_q), NULL, this);
|
||||
exportsbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_export_s), NULL, this);
|
||||
importobutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_import_o), NULL, this);
|
||||
importpbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_import_p), NULL, this);
|
||||
importqbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_import_q), NULL, this);
|
||||
changetsbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
wxCommandEventHandler(wxeditor_voicesub::on_change_ts), NULL, this);
|
||||
loadbutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
|
||||
|
@ -153,6 +163,7 @@ void wxeditor_voicesub::on_select(wxCommandEvent& e)
|
|||
deletebutton->Enable(valid);
|
||||
exportobutton->Enable(valid);
|
||||
exportpbutton->Enable(valid);
|
||||
exportqbutton->Enable(valid);
|
||||
changetsbutton->Enable(valid);
|
||||
}
|
||||
|
||||
|
@ -194,7 +205,7 @@ void wxeditor_voicesub::on_export_o(wxCommandEvent& e)
|
|||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_export_stream(id, filename, true);
|
||||
voicesub_export_stream(id, filename, EXTFMT_OPUSDEMO);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
|
@ -213,7 +224,26 @@ void wxeditor_voicesub::on_export_p(wxCommandEvent& e)
|
|||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_export_stream(id, filename, false);
|
||||
voicesub_export_stream(id, filename, EXTFMT_SOX);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void wxeditor_voicesub::on_export_q(wxCommandEvent& e)
|
||||
{
|
||||
uint64_t id = get_id();
|
||||
if(id == NOTHING)
|
||||
return;
|
||||
try {
|
||||
std::string filename;
|
||||
try {
|
||||
filename = pick_file(this, "Select Ogg (Opus) file to export", ".", true);
|
||||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_export_stream(id, filename, EXTFMT_OGGOPUS);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error exporting", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
|
@ -248,7 +278,7 @@ void wxeditor_voicesub::on_import_o(wxCommandEvent& e)
|
|||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_import_stream(ts, filename, true);
|
||||
voicesub_import_stream(ts, filename, EXTFMT_OPUSDEMO);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
|
@ -267,7 +297,26 @@ void wxeditor_voicesub::on_import_p(wxCommandEvent& e)
|
|||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_import_stream(ts, filename, false);
|
||||
voicesub_import_stream(ts, filename, EXTFMT_SOX);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
refresh();
|
||||
}
|
||||
|
||||
void wxeditor_voicesub::on_import_q(wxCommandEvent& e)
|
||||
{
|
||||
try {
|
||||
std::string filename;
|
||||
uint64_t ts;
|
||||
try {
|
||||
ts = voicesub_parse_timebase(pick_text(this, "Enter timebase", "Enter position for newly "
|
||||
"imported stream"));
|
||||
filename = pick_file(this, "Select Ogg (Opus) file to import", ".", false);
|
||||
} catch(...) {
|
||||
return;
|
||||
}
|
||||
voicesub_import_stream(ts, filename, EXTFMT_OGGOPUS);
|
||||
} catch(std::exception& e) {
|
||||
show_message_ok(this, "Error importing", e.what(), wxICON_EXCLAMATION);
|
||||
}
|
||||
|
@ -334,6 +383,7 @@ void wxeditor_voicesub::refresh()
|
|||
exportsbutton->Enable(cflag);
|
||||
importobutton->Enable(cflag);
|
||||
importpbutton->Enable(cflag);
|
||||
importqbutton->Enable(cflag);
|
||||
int sel = subtitles->GetSelection();
|
||||
subtitles->Clear();
|
||||
smap.clear();
|
||||
|
|
36
src/util/lsnes-dumpogg.cpp
Normal file
36
src/util/lsnes-dumpogg.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#include "library/ogg.hpp"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if(argc != 2) {
|
||||
std::cerr << "Filename needed." << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::ifstream s(argv[1], std::ios_base::binary);
|
||||
if(!s) {
|
||||
std::cerr << "Can't open '" << argv[1] << "'" << std::endl;
|
||||
return 2;
|
||||
}
|
||||
ogg_stream_reader_iostreams r(s);
|
||||
ogg_page p;
|
||||
while(r.get_page(p)) {
|
||||
std::cout << "Ogg page: Stream " << p.get_stream() << " sequence " << p.get_sequence()
|
||||
<< " Flags: " << (p.get_continue() ? "CONTINUE " : "")
|
||||
<< (p.get_bos() ? "BOS " : "") << (p.get_eos() ? "EOS " : "")
|
||||
<< "granulepos=" << p.get_granulepos() << std::endl;
|
||||
size_t pc = p.get_packet_count();
|
||||
for(size_t i = 0; i < pc; i++) {
|
||||
auto pp = p.get_packet(i);
|
||||
std::cout << "Packet #" << i << ": " << pp.second << " bytes";
|
||||
if(i == 0 && p.get_continue())
|
||||
std::cout << " <continued>";
|
||||
if(i + 1 == pc && p.get_last_packet_incomplete())
|
||||
std::cout << " <incomplete>";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "End of Ogg stream." << std::endl;
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue