Refactor .avi dumping

Extract the sample rate reduction and SOX writing code from AVI writing
code. Place sample rate reduction to AVI dump control code and split
SOX code to its own class.
This commit is contained in:
Ilari Liusvaara 2011-09-16 11:25:55 +03:00
parent b4d18f7309
commit 247a1ba33a
6 changed files with 227 additions and 140 deletions

View file

@ -4,7 +4,7 @@ HOSTCC = $(CC)
OBJECTS = controllerdata.o fieldsplit.o memorymanip.o misc.o movie.o moviefile.o render.o rom.o zip.o fonts/font.o keymapper.o window.o window-sdl.o settings.o framerate.o mainloop.o rrdata.o png.o lsnesrc.o memorywatch.o command.o avsnoop.o moviedata.o controller.o framebuffer.o
#AVI dumper
OBJECTS += avidump/avidump-control.o avidump/avidump.o
OBJECTS += avidump/avidump-control.o avidump/avidump.o avidump/sox.o
PROGRAMS = lsnes.exe movietrunctest.exe

View file

@ -1,5 +1,6 @@
#include "lua.hpp"
#include "avidump.hpp"
#include "sox.hpp"
#include "settings.hpp"
#include <iomanip>
#include <cassert>
@ -24,11 +25,14 @@ namespace
avi_avsnoop(const std::string& prefix, struct avi_info parameters) throw(std::bad_alloc)
{
vid_dumper = new avidumper(prefix, parameters);
soxdumper = new sox_dumper(prefix + ".sox", 32040.5, 2);
dcounter = 0;
}
~avi_avsnoop() throw()
{
delete vid_dumper;
delete soxdumper;
}
void frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d, window* win, bool dummy)
@ -68,12 +72,18 @@ namespace
void sample(short l, short r) throw(std::bad_alloc, std::runtime_error)
{
vid_dumper->on_sample(l, r);
dcounter += 81;
if(dcounter < 64081)
vid_dumper->on_sample(l, r);
else
dcounter -= 64081;
soxdumper->sample(l, r);
}
void end() throw(std::bad_alloc, std::runtime_error)
{
vid_dumper->on_end();
soxdumper->close();
}
void gameinfo(const std::string& gamename, const std::list<std::pair<std::string, std::string>>&
@ -84,7 +94,9 @@ namespace
}
private:
avidumper* vid_dumper;
sox_dumper* soxdumper;
screen dscr;
unsigned dcounter;
};
avi_avsnoop* vid_dumper;
@ -114,10 +126,7 @@ namespace
}
struct avi_info parameters;
parameters.compression_level = (level2 > 9) ? (level2 - 9) : level2;
parameters.audio_drop_counter_inc = 81;
parameters.audio_drop_counter_max = 64081;
parameters.audio_sampling_rate = 32000;
parameters.audio_native_sampling_rate = 32040.5;
parameters.keyframe_interval = (level2 > 9) ? 300 : 1;
try {
vid_dumper = new avi_avsnoop(prefix, parameters);

View file

@ -55,40 +55,6 @@ namespace
return 1;
}
};
void write_double(uint8_t* buf, double v)
{
unsigned mag = 1023;
while(v >= 2) {
mag++;
v /= 2;
}
while(v < 1) {
mag--;
v *= 2;
}
uint64_t v2 = mag;
v -= 1;
for(unsigned i = 0; i < 52; i++) {
v *= 2;
v2 = 2 * v2 + ((v >= 1) ? 1 : 0);
if(v >= 1)
v -= 1;
}
buf[0] = v2;
buf[1] = v2 >> 8;
buf[2] = v2 >> 16;
buf[3] = v2 >> 24;
buf[4] = v2 >> 32;
buf[5] = v2 >> 40;
buf[6] = v2 >> 48;
buf[7] = v2 >> 56;
}
// buffer[20] = 0x20; //Rate.
// buffer[21] = 0x4A; //Rate.
// buffer[22] = 0xDF; //Rate.
// buffer[23] = 0x40; //Rate.
}
int avidumper::encode_thread()
@ -110,13 +76,9 @@ int avidumper::encode_thread()
avidumper::avidumper(const std::string& _prefix, struct avi_info parameters)
{
compression_level = parameters.compression_level;
audio_drop_counter_inc = parameters.audio_drop_counter_inc;
audio_drop_counter_max = parameters.audio_drop_counter_max;
audio_sampling_rate = parameters.audio_sampling_rate;
audio_native_sampling_rate = parameters.audio_native_sampling_rate;
keyframe_interval = parameters.keyframe_interval;
sox_open = false;
avi_open = false;
capture_error = false;
pwidth = 0xFFFF;
@ -128,8 +90,6 @@ avidumper::avidumper(const std::string& _prefix, struct avi_info parameters)
total_data = 0;
total_frames = 0;
total_samples = 0;
audio_drop_counter = 0;
raw_samples = 0;
audio_put_ptr = 0;
audio_get_ptr = 0;
audio_commit_ptr = 0;
@ -163,37 +123,6 @@ void avidumper::on_sample(short left, short right) throw(std::bad_alloc, std::ru
audio_buffer[audio_put_ptr++] = right;
if(audio_put_ptr == AVIDUMPER_AUDIO_BUFFER)
audio_put_ptr = 0;
//Don't write secondary audio if primary is of perfect quality.
if(!audio_drop_counter_inc)
return;
if(!sox_open) {
sox_stream.open(prefix + ".sox", std::ios::out | std::ios::binary);
if(!sox_stream)
throw std::runtime_error("Can't open audio stream");
uint8_t buffer[32] = {0};
buffer[0] = 0x2E; //Magic.
buffer[1] = 0x53; //Magic.
buffer[2] = 0x6F; //Magic.
buffer[3] = 0x58; //Magic.
buffer[4] = 0x1C; //Header size.
write_double(buffer + 16, audio_native_sampling_rate);
buffer[24] = 0x02; //Stereo.
sox_stream.write(reinterpret_cast<char*>(buffer), 32);
if(!sox_stream)
throw std::runtime_error("Can't write audio header");
sox_open = true;
}
uint8_t buffer[8] = {0};
buffer[2] = static_cast<unsigned short>(left) & 0xFF;
buffer[3] = static_cast<unsigned short>(left) >> 8;
buffer[6] = static_cast<unsigned short>(right) & 0xFF;
buffer[7] = static_cast<unsigned short>(right) >> 8;
sox_stream.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_stream)
throw std::runtime_error("Can't write audio sample");
raw_samples += 2;
}
void avidumper::on_frame(const uint32_t* data, uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d)
@ -253,9 +182,6 @@ void avidumper::print_summary(std::ostream& str)
double global_alength = static_cast<double>(total_samples) / audio_sampling_rate;
uint64_t local_size = segment_movi_ptr + 352 + 16 * segment_chunks.size();
uint64_t global_size = total_data + 8 + 16 * segment_chunks.size();
uint64_t global_a2frames = raw_samples / 2;
double global_a2length = raw_samples / (2.0 * audio_native_sampling_rate);
std::ostringstream s2;
@ -270,10 +196,6 @@ void avidumper::print_summary(std::ostream& str)
<< fmtdbl(global_alength - global_vlength, 10) << "|" << std::endl;
s2 << "Size | " << fmtint(local_size, 10) << "| "
<< fmtint(global_size, 10) << "|" << std::endl;
s2 << "Audio2 stream | N/A|"
<< fmtint(global_a2frames, 10) << "/" << fmtdbl(global_a2length, 10) << "|" << std::endl;
s2 << "A2/V desync | N/A| "
<< fmtdbl(global_a2length - global_vlength, 10) << "|" << std::endl;
s2 << "----------------+---------------------+---------------------+" << std::endl;
str << s2.str();
@ -368,22 +290,6 @@ void avidumper::on_end() throw(std::bad_alloc, std::runtime_error)
flush_audio_to(audio_put_ptr);
fixup_avi_header_and_close();
if(sox_open) {
sox_stream.seekp(8, std::ios::beg);
uint8_t buffer[8];
buffer[0] = raw_samples;
buffer[1] = raw_samples >> 8;
buffer[2] = raw_samples >> 16;
buffer[3] = raw_samples >> 24;
buffer[4] = raw_samples >> 32;
buffer[5] = raw_samples >> 40;
buffer[6] = raw_samples >> 48;
buffer[7] = raw_samples >> 56;
sox_stream.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_stream)
throw std::runtime_error("Can't fixup audio header");
sox_stream.close();
}
}
namespace
@ -484,14 +390,10 @@ void avidumper::flush_audio_to(unsigned commit_to)
//Count the number of samples to actually write.
unsigned samples_to_write = 0;
unsigned adc = audio_drop_counter;
unsigned aptr = audio_get_ptr;
unsigned idx = 8;
while(aptr != commit_to) {
if((adc += audio_drop_counter_inc) >= audio_drop_counter_max)
adc -= audio_drop_counter_max;
else
samples_to_write++;
samples_to_write++;
if((aptr += 2) == AVIDUMPER_AUDIO_BUFFER)
aptr = 0;
}
@ -508,20 +410,14 @@ void avidumper::flush_audio_to(unsigned commit_to)
buf[7] = (4 * samples_to_write) >> 24;
while(audio_get_ptr != commit_to) {
if((audio_drop_counter += audio_drop_counter_inc) >= audio_drop_counter_max) {
//Don't write this sample, reset the counter. This is so that the output is at the correct
//rate.
audio_drop_counter -= audio_drop_counter_max;
} else {
//Write sample.
buf[idx] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]);
buf[idx + 1] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]) >> 8;
buf[idx + 2] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]);
buf[idx + 3] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]) >> 8;
idx += 4;
segment_samples++;
total_samples++;
}
//Write sample.
buf[idx] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]);
buf[idx + 1] = static_cast<unsigned short>(audio_buffer[audio_get_ptr]) >> 8;
buf[idx + 2] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]);
buf[idx + 3] = static_cast<unsigned short>(audio_buffer[audio_get_ptr + 1]) >> 8;
idx += 4;
segment_samples++;
total_samples++;
if((audio_get_ptr += 2) == AVIDUMPER_AUDIO_BUFFER)
audio_get_ptr = 0;
}

View file

@ -142,26 +142,11 @@ struct avi_info
*/
unsigned compression_level;
/**
* Audio drop counter increments by this much every frame.
*/
uint64_t audio_drop_counter_inc;
/**
* Audio drop counter modulus (when audio drop counter warps around, sample is dropped).
*/
uint64_t audio_drop_counter_max;
/**
* Audio sampling rate to write to AVI.
*/
uint32_t audio_sampling_rate;
/**
* Native audio sampling rate to write to auxillary SOX file.
*/
double audio_native_sampling_rate;
/**
* Interval of keyframes (WARNING: >1 gives non-keyframes which AVISource() doesn't like).
*/
@ -234,18 +219,13 @@ private:
void flush_audio_to(unsigned commit_ptr);
void open_and_write_avi_header(uint16_t width, uint16_t height, uint32_t fps_n, uint32_t fps_d);
void fixup_avi_header_and_close();
std::ofstream sox_stream;
std::ofstream avi_stream;
bool capture_error;
std::string capture_error_str;
bool sox_open;
bool avi_open;
//Global settings.
unsigned compression_level;
uint64_t audio_drop_counter_inc;
uint64_t audio_drop_counter_max;
uint32_t audio_sampling_rate;
double audio_native_sampling_rate;
uint32_t keyframe_interval;
//Previous frame.
uint16_t pwidth;
@ -269,8 +249,6 @@ private:
uint64_t total_data;
uint64_t total_frames;
uint64_t total_samples;
uint64_t raw_samples;
uint64_t audio_drop_counter;
//Temporary buffers.
std::vector<uint8_t> pframe;
std::vector<uint8_t> tframe;

105
avidump/sox.cpp Normal file
View file

@ -0,0 +1,105 @@
#include "sox.hpp"
#include <iostream>
namespace
{
void write_double(uint8_t* buf, double v)
{
unsigned mag = 1023;
while(v >= 2) {
mag++;
v /= 2;
}
while(v < 1) {
mag--;
v *= 2;
}
uint64_t v2 = mag;
v -= 1;
for(unsigned i = 0; i < 52; i++) {
v *= 2;
v2 = 2 * v2 + ((v >= 1) ? 1 : 0);
if(v >= 1)
v -= 1;
}
buf[0] = v2;
buf[1] = v2 >> 8;
buf[2] = v2 >> 16;
buf[3] = v2 >> 24;
buf[4] = v2 >> 32;
buf[5] = v2 >> 40;
buf[6] = v2 >> 48;
buf[7] = v2 >> 56;
}
}
sox_dumper::sox_dumper(const std::string& filename, double samplerate, uint32_t channels) throw(std::bad_alloc,
std::runtime_error)
{
sox_file.open(filename.c_str(), std::ios::out | std::ios::binary);
if(!sox_file)
throw std::runtime_error("Can't open sox file for output");
try {
uint8_t buffer[32] = {0};
buffer[0] = 0x2E; //Magic.
buffer[1] = 0x53; //Magic.
buffer[2] = 0x6F; //Magic.
buffer[3] = 0x58; //Magic.
buffer[4] = 0x1C; //Header size.
write_double(buffer + 16, samplerate);
buffer[24] = channels;
buffer[25] = channels >> 8;
buffer[26] = channels >> 16;
buffer[27] = channels >> 24;
sox_file.write(reinterpret_cast<char*>(buffer), 32);
if(!sox_file)
throw std::runtime_error("Can't write audio header");
samplebuffer.resize(channels);
databuf.resize(channels << 2);
} catch(...) {
sox_file.close();
throw;
}
}
sox_dumper::~sox_dumper() throw()
{
try {
close();
} catch(...) {
}
}
void sox_dumper::close() throw(std::bad_alloc, std::runtime_error)
{
sox_file.seekp(8, std::ios::beg);
uint8_t buffer[8];
uint64_t raw_samples = samples_dumped * samplebuffer.size();
buffer[0] = raw_samples;
buffer[1] = raw_samples >> 8;
buffer[2] = raw_samples >> 16;
buffer[3] = raw_samples >> 24;
buffer[4] = raw_samples >> 32;
buffer[5] = raw_samples >> 40;
buffer[6] = raw_samples >> 48;
buffer[7] = raw_samples >> 56;
sox_file.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_file)
throw std::runtime_error("Can't fixup audio header");
sox_file.close();
}
void sox_dumper::internal_dump_sample()
{
for(size_t i = 0; i < samplebuffer.size(); ++i) {
uint32_t v = samplebuffer[i];
databuf[4 * i + 0] = v;
databuf[4 * i + 1] = v >> 8;
databuf[4 * i + 2] = v >> 16;
databuf[4 * i + 3] = v >> 24;
}
sox_file.write(&databuf[0], databuf.size());
if(!sox_file)
throw std::runtime_error("Failed to dump sample");
samples_dumped++;
}

99
avidump/sox.hpp Normal file
View file

@ -0,0 +1,99 @@
#ifndef _sox__hpp__included__
#define _sox__hpp__included__
#include <cstdint>
#include <fstream>
#include <stdexcept>
#include <vector>
/**
* .sox sound dumper.
*/
class sox_dumper
{
public:
/**
* Create new dumper with specified sound sampling rate and channel count.
*
* parameter filename: The name of file to dump to.
* parameter samplerate: The sampling rate (must be positive)
* parameter channels: The channel count.
*
* throws std::bad_alloc: Not enough memory
* throws std::runtime_error: Error opening .sox file
*/
sox_dumper(const std::string& filename, double samplerate, uint32_t channels) throw(std::bad_alloc,
std::runtime_error);
/**
* Destructor.
*/
~sox_dumper() throw();
/**
* Close the dump.
*
* throws std::bad_alloc: Not enough memory
* throws std::runtime_error: Error fixing and closing .sox file
*/
void close() throw(std::bad_alloc, std::runtime_error);
/**
* Dump a sample
*
* parameters a: Sample channel values.
*/
template<typename... args>
void sample(args... a)
{
sample2<0>(a...);
}
private:
template<size_t o>
void sample2()
{
for(size_t i = o; i < samplebuffer.size(); ++i)
samplebuffer[i] = 0;
internal_dump_sample();
}
template<size_t o, typename itype, typename... args>
void sample2(itype v, args... a)
{
if(o < samplebuffer.size())
place_sample(o, v);
sample2<o + 1>(a...);
}
void place_sample(size_t index, int8_t v)
{
samplebuffer[index] = static_cast<int32_t>(v) << 24;
}
void place_sample(size_t index, uint8_t v)
{
place_sample(index, static_cast<uint32_t>(v) << 24);
}
void place_sample(size_t index, int16_t v)
{
samplebuffer[index] = static_cast<int32_t>(v) << 16;
}
void place_sample(size_t index, uint16_t v)
{
place_sample(index, static_cast<uint32_t>(v) << 16);
}
void place_sample(size_t index, int32_t v)
{
samplebuffer[index] = v;
}
void place_sample(size_t index, uint32_t v)
{
place_sample(index, static_cast<int32_t>(v + 0x80000000U));
}
void internal_dump_sample();
std::vector<char> databuf;
std::vector<int32_t> samplebuffer;
std::ofstream sox_file;
uint64_t samples_dumped;
};
#endif