Refactor AVI dumping a lot (WIP)

This commit is contained in:
Ilari Liusvaara 2012-02-12 20:23:36 +02:00
parent fab8d8f4e3
commit 043cac8894
26 changed files with 2030 additions and 1211 deletions

View file

@ -25,6 +25,24 @@ SOUND=SDL
JOYSTICK=SDL
THREADS=BOOST
#Threads
ifdef THREADS
ifeq ($(THREADS), NATIVE)
CFLAGS += -DNATIVE_THREADS
else
ifeq ($(THREADS), BOOST)
CFLAGS += -DBOOST_THREADS
ifdef BOOST_THREAD_LIB
LDFLAGS += -l$(BOOST_THREAD_LIB)
else
LDFLAGS += -lboost_thread-mt
endif
else
$(error "Bad value for THREADS (expected NATIVE or BOOST)")
endif
endif
endif
ifdef BSNES_IS_COMPAT
CFLAGS += -DBSNES_IS_COMPAT
endif

View file

@ -0,0 +1,112 @@
#ifndef _library_workthread__hpp__included__
#define _library_workthread__hpp__included__
#include <cstdint>
#ifdef NATIVE_THREADS
#include <thread>
#include <condition_variable>
#include <mutex>
typedef std::thread thread_class;
typedef std::condition_variable cv_class;
typedef std::mutex mutex_class;
typedef std::unique_lock<std::mutex> umutex_class;
#else
#include <boost/thread.hpp>
#include <boost/thread/locks.hpp>
typedef boost::thread thread_class;
typedef boost::condition_variable cv_class;
typedef boost::mutex mutex_class;
typedef boost::unique_lock<boost::mutex> umutex_class;
#endif
#define WORKFLAG_QUIT_REQUEST 0x80000000U
class worker_thread_reflector;
/**
* A worker thread.
*
* Note: All methods (except entrypoints) are thread-safe.
*/
class worker_thread
{
public:
/**
* Constructor.
*/
worker_thread();
/**
* Destructor.
*/
~worker_thread();
/**
* Request quit. Sets quit request workflag.
*/
void request_quit();
/**
* Set the busy flag.
*/
void set_busy();
/**
* Clear the busy flag.
*/
void clear_busy();
/**
* Wait until busy flag cleared.
*/
void wait_busy();
/**
* Rethrow caught exception if any.
*/
void rethrow();
/**
* Set work flag.
*
* Parameter flag: The flags to set.
*/
void set_workflag(uint32_t flag);
/**
* Clear work flag.
*
* Parameter flag: Work flags to clear.
* Returns: The workflags before clearing.
*/
uint32_t clear_workflag(uint32_t flag);
/**
* Wait until work flags nonzero.
*
* Returns: Current work flags.
*/
uint32_t wait_workflag();
/**
* Thread raw entrypoint.
*
* Note: Don't call from outside workthread code.
*/
int operator()(int dummy);
protected:
/**
* Thread entrypoint.
*
* Notes: Exceptions thrown are catched.
*/
virtual void entry() = 0;
/**
* Start actually running the thread.
*/
void fire();
private:
thread_class* thread;
worker_thread_reflector* reflector;
cv_class condition;
mutex_class mutex;
volatile bool joined;
volatile uint32_t workflag;
volatile bool busy;
volatile bool exception_caught;
volatile bool exception_oom;
std::string exception_text;
};
#endif

275
include/video/avi/codec.hpp Normal file
View file

@ -0,0 +1,275 @@
#ifndef _avi__avi_codec__hpp__included__
#define _avi__avi_codec__hpp__included__
#include <cstdint>
#include <vector>
#include <cstdlib>
#include "video/avi/structure.hpp"
#include "video/avi/samplequeue.hpp"
#include "video/avi/timer.hpp"
/**
* AVI packet.
*/
struct avi_packet
{
/**
* Packet type code. The usual values are:
* - 0x6264: Uncompressed bitmap
* - 0x6364: compressed bitmap
* - 0x6277: Sound data
*/
uint16_t typecode;
/**
* Hide from index flag.
*/
bool hidden;
/**
* Index flags. Ignored if hidden flag is set.
*/
uint32_t indexflags;
/**
* The actual packet payload.
*/
std::vector<char> payload;
};
/**
* AVI video codec (compressor).
*/
struct avi_video_codec
{
/**
* Strf info
*/
struct format
{
format(uint32_t compression, uint16_t bitcount);
uint32_t suggested_buffer_size;
uint32_t max_bytes_per_sec;
uint16_t planes;
uint16_t bit_count;
uint32_t compression;
uint32_t resolution_x;
uint32_t resolution_y;
uint32_t quality;
uint32_t clr_used;
uint32_t clr_important;
std::vector<uint8_t> extra;
};
virtual ~avi_video_codec();
/**
* Reset the codec, giving new state.
*
* Parameter width: The width of image.
* Parameter height: The height of image.
* Parameter fps_n: fps numerator.
* Parameter fps_d: fps denominator.
* Returns: Stream format.
*/
virtual format reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d) = 0;
/**
* Send in frame of data. ready() must return true.
*
* - The buffer can be reused after this call returns.
* - rshift = 0, gshift = 8, bshift = 16.
*
* Parameter data: Video frame data, left to right, top to bottom order.
*/
virtual void frame(uint32_t* data) = 0;
/**
* Is the codec ready to receive a new frame?
*
* Returns: True if new frame can be passed. False if packets have to be extracted.
*/
virtual bool ready() = 0;
/**
* Read packet. Before calling, ready() must return false.
*
* Returns: The packet.
*/
virtual avi_packet getpacket() = 0;
};
/**
* AVI common codec type.
*/
template<typename T>
struct avi_codec_type
{
avi_codec_type(const char* iname, const char* hname, T* (*instance)());
/**
* Unregister instance.
*/
~avi_codec_type();
/**
* Find instance.
*
* Parameter iname: Iname of instance to find.
*/
static avi_codec_type<T>* find(const std::string& iname);
/**
* Find next codec type.
*
* Parameter type: Type to find. If NULL, find the first one.
* Returns: The next codec, or NULL if none.
*/
static avi_codec_type<T>* find_next(avi_codec_type<T>* type);
/**
* Get iname field of codec.
*/
std::string get_iname();
/**
* Get hname field of codec.
*/
std::string get_hname();
/**
* Get instance of codec.
*/
T* get_instance();
private:
std::string iname;
std::string hname;
T* (*instance)();
static std::map<std::string, avi_codec_type<T>*>& codecs();
};
/**
* AVI audio codec (compressor).
*/
struct avi_audio_codec
{
/**
* Strf info.
*/
struct format
{
format(uint16_t tag);
uint32_t max_bytes_per_sec;
uint32_t suggested_buffer_size;
uint16_t format_tag;
uint32_t average_rate;
uint16_t alignment;
uint16_t bitdepth;
uint32_t quality;
std::vector<uint8_t> extra;;
};
virtual ~avi_audio_codec();
/**
* Reset the codec, giving new state.
*
* Parameter samplerate: The new sampling rate.
* Parameter channels: Channel count.
* Returns: Stream format.
*/
virtual format reset(uint32_t samplerate, uint16_t channels) = 0;
/**
* Send in samples of data. ready() must return true.
*
* - The buffer can be reused after this call returns.
*
* Parameter data: Interleaved samples.
* Parameter samples: Number of samples offered.
*/
virtual void samples(int16_t* data, size_t samples) = 0;
/**
* Is the codec ready to receive a new frame?
*
* Returns: True if new frame can be passed. False if packets have to be extracted.
*/
virtual bool ready() = 0;
/**
* Read packet. Before calling, ready() must return false.
*
* Returns: The packet.
*/
virtual avi_packet getpacket() = 0;
};
typedef avi_codec_type<avi_video_codec> avi_video_codec_type;
typedef avi_codec_type<avi_audio_codec> avi_audio_codec_type;
/**
* Combine trackid and packet typecode into full type.
*/
uint32_t get_actual_packet_type(uint8_t trackid, uint16_t typecode);
/**
* AVI output stream.
*/
struct avi_output_stream
{
/**
* Create new output stream.
*/
avi_output_stream();
/**
* Destructor.
*/
~avi_output_stream();
/**
* Start new segment. If there is existing segment, it is closed.
*
* Parameter out: Output file.
* Parameter vcodec: The video codec to use.
* Parameter acodec: The audio codec to use.
* Parameter width: Width of video.
* Parameter height: Height of video.
* Parameter fps_n: Framerate numerator.
* Parameter fps_d: Framerate denomerator.
* Parameter samplerate: Audio sampling rate.
* Parameter channels: Number of audio channels.
*/
void start(std::ostream& out, avi_video_codec& vcodec, avi_audio_codec& acodec, uint32_t width,
uint32_t height, uint32_t fps_n, uint32_t fps_d, uint32_t samplerate, uint16_t channels);
/**
* Write stuff to video codec.
*
* Parameter frame: The frame to write. See avi_video_codec::frame() for format.
*/
void frame(uint32_t* frame);
/**
* Write stuff to audio codec.
*
* Parameter samples: The samples to write.
* Parameter samplecount: Count of samples.
*/
void samples(int16_t* samples, size_t samplecount);
/**
* Get number of samples for the next frame.
*
* Returns: The number of samples.
*/
size_t framesamples();
/**
* Get size estimate.
*
* Returns: The estimated size.
*/
uint64_t get_size_estimate();
/**
* Flush frame and associtated samples from queue.
*
* Parameter frame: The frame to write. Deleted if written.
* Parameter aqueue: The audio queue.
* Parameter force: Read the frame even if there aren't enough sound samples.
* Returns: True if frame was read, false otherwise.
*/
bool readqueue(uint32_t* frame, sample_queue& aqueue, bool force);
/**
* End a segment.
*/
void end();
private:
bool in_segment;
avi_file_structure avifile;
avi_video_codec* vcodec;
avi_audio_codec* acodec;
uint16_t achans;
timer video_timer;
timer audio_timer;
};
#endif

View file

@ -0,0 +1,63 @@
#ifndef _avi__samplequeue__hpp__included__
#define _avi__samplequeue__hpp__included__
#include <deque>
#include <cstdint>
#include <vector>
#include <cstdlib>
#include "library/workthread.hpp"
/**
* Sample queue.
*/
class sample_queue
{
public:
/**
* Construct new sample queue.
*/
sample_queue();
/**
* Push samples into queue.
*
* Parameter samples: The samples to push
* Parameter count: The number of samples (assumed mono) to push.
* Note: This is thread safe.
*/
void push(const int16_t* samples, size_t count);
/**
* Pull samples from queue.
*
* Parameter samples: The pulled samples are stored here.
* Parameter count: The number of samples (assumed mono) to pull.
* Note: This is thread safe.
* Note: Trying to pull nonexistent samples causes zeros to be pulled.
*/
void pull(int16_t* samples, size_t count);
/**
* Get number of available samples.
*
* Returns: Number of available samples.
* Note: This is thread safe.
*/
size_t available();
private:
std::vector<int16_t> data;
bool blank;
size_t rptr;
size_t wptr;
size_t size;
mutex_class lock;
};
struct frame_object
{
uint32_t* data;
uint32_t width;
uint32_t height;
uint32_t fps_n;
uint32_t fps_d;
bool force_break;
};
#endif

View file

@ -101,6 +101,7 @@ struct stream_header
stream_header();
void add_frames(size_t count);
void serialize(std::ostream& out, struct stream_format_base& format);
void reset();
};
template<class format>
@ -110,6 +111,7 @@ struct stream_header_list
stream_header strh;
format strf;
void serialize(std::ostream& out);
void reset();
};
struct avi_header
@ -131,6 +133,7 @@ struct header_list
stream_header_list<stream_format_video> videotrack;
stream_header_list<stream_format_audio> audiotrack;
void serialize(std::ostream& out);
void reset();
};
struct movi_chunk
@ -141,6 +144,7 @@ struct movi_chunk
movi_chunk();
void add_payload(size_t s);
void serialize(std::ostream& out);
void reset();
};
struct index_entry
@ -160,6 +164,7 @@ struct idx1_chunk
std::list<index_entry> entries;
size_t size();
void serialize(std::ostream& out);
void reset();
};
struct avi_file_structure
@ -169,9 +174,10 @@ struct avi_file_structure
header_list hdrl;
movi_chunk movi;
idx1_chunk idx1;
void serialize(std::ostream& out);
std::ostream* outstream;
void serialize();
void start_data(std::ostream& out);
void finish_avi(std::ostream& out);
void finish_avi();
};
#endif

View file

@ -0,0 +1,33 @@
#ifndef _avi__timer__hpp__included__
#define _avi__timer__hpp__included__
#include <cstdint>
class timer
{
public:
timer(uint32_t rate_n, uint32_t rate_d = 1);
void rate(uint32_t rate_n, uint32_t rate_d = 1);
void increment()
{
w += sw;
n += sn;
w += (n / d);
n %= d;
}
uint64_t read()
{
return w;
}
uint64_t read_next();
void reset();
private:
void set_step(uint32_t rate_n, uint32_t rate_d);
uint64_t w;
uint64_t n;
uint64_t d;
uint64_t sw;
uint64_t sn;
};
#endif

View file

@ -0,0 +1,60 @@
#ifndef _avi__avi_writer__hpp__included__
#define _avi__avi_writer__hpp__included__
#include <string>
#include <fstream>
#include "video/avi/codec.hpp"
#include "samplequeue.hpp"
class avi_writer
{
public:
/**
* Create new avi writer.
*
* Parameter _prefix: The prefix to use.
* Parameter _vcodec: The video codec.
* Parameter _acodec: The audio codec.
*/
avi_writer(const std::string& _prefix, struct avi_video_codec& _vcodec, struct avi_audio_codec& _acodec,
uint32_t samplerate, uint16_t audiochannels);
/**
* Destructor.
*/
~avi_writer();
/**
* Get the video queue.
*/
std::deque<frame_object>& video_queue();
/**
* Get the audio queue.
*/
sample_queue& audio_queue();
/**
* Flush the queue.
*/
void flush();
/**
* Force close the segment. Impiles forced flush (flush even if no sound samples for it).
*/
void close();
private:
void flush(bool force);
bool closed;
std::string prefix;
uint64_t next_segment;
std::deque<frame_object> vqueue;
sample_queue aqueue;
avi_output_stream aviout;
std::ofstream avifile;
struct avi_video_codec& vcodec;
struct avi_audio_codec& acodec;
uint32_t samplerate;
uint16_t channels;
uint32_t curwidth;
uint32_t curheight;
uint32_t curfps_n;
uint32_t curfps_d;
};
#endif

View file

@ -1,281 +0,0 @@
#ifndef _output_cscd__hpp__included__
#define _output_cscd__hpp__included__
#ifdef NATIVE_THREADS
#include <thread>
#include <condition_variable>
#include <mutex>
typedef std::thread thread_class;
typedef std::condition_variable cv_class;
typedef std::mutex mutex_class;
typedef std::unique_lock<std::mutex> umutex_class;
#else
#include <boost/thread.hpp>
#include <boost/thread/locks.hpp>
typedef boost::thread thread_class;
typedef boost::condition_variable cv_class;
typedef boost::mutex mutex_class;
typedef boost::unique_lock<boost::mutex> umutex_class;
#endif
#include <cstdint>
#include <stdexcept>
#include <cstdlib>
#include <vector>
#include <fstream>
#include <list>
struct avi_file_structure;
/**
* Dump AVI using CSCD for video and PCM for audio.
*/
class avi_cscd_dumper
{
public:
/**
* AVI dumper parameters.
*/
struct global_parameters
{
/**
* Sound sampling rate.
*/
unsigned long sampling_rate;
};
/**
* Pixel formats
*/
enum pixelformat
{
PIXFMT_RGBX, /* 32-bit RGB, RGBx order. */
PIXFMT_BGRX, /* 32-bit RGB, BGRx order. */
PIXFMT_XRGB, /* 32-bit RGB, BGRx order. */
PIXFMT_XBGR /* 32-bit RGB, xBGR order. */
};
/**
* AVI per-segment parameters
*/
struct segment_parameters
{
/**
* Framerate numerator.
*/
unsigned long fps_n;
/**
* Framerate denominator.
*/
unsigned long fps_d;
/**
* Pixel format.
*/
enum pixelformat dataformat;
/**
* Picture width
*/
unsigned width;
/**
* Picture height
*/
unsigned height;
/**
* If TRUE, always use default stride (bytes-per-pixel * width)
*/
bool default_stride;
/**
* Picture stride in bytes.
*/
size_t stride;
/**
* Keyframe distance (1 => every frame is keyframe).
*/
unsigned keyframe_distance;
/**
* Deflate compression level (0-9)
*/
unsigned deflate_level;
/**
* Maximum number of frames per major segment.
*/
unsigned long max_segment_frames;
};
/**
* Create new dumper.
*
* Parameter prefix: Prefix for dumped files.
* Parameter global: Global dumper parameters.
* Parameter segment: Dumper segment parameters.
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Illegal parameters.
*
* Note: Segment parameters have to be sane, but altering those before dumping the first frame does not cause
* extraneous segment.
*/
avi_cscd_dumper(const std::string& prefix, const global_parameters& global, const segment_parameters& segment)
throw(std::bad_alloc, std::runtime_error);
/**
* Try to close the dump.
*/
~avi_cscd_dumper() throw();
/**
* Get current segment parameters.
*
* Returns: The segment parameters
*/
segment_parameters get_segment_parameters() throw();
/**
* Set segment parameters.
*
* Parameter segment: New segment parameters.
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Illegal parameters.
*
* Note: If parameters change in incompatible manner, next dumped frame will cause segment change. The following
* changes are incompatible:
* - Changing the framerate.
* - Changing data format between 15 and 24/32 bit formats.
* - Changing with and height.
*/
void set_segment_parameters(const segment_parameters& segment) throw(std::bad_alloc, std::runtime_error);
/**
* Dump a frame.
*
* Parameter framedata: The frame data, in left-to-right, top-to-bottom order. Pixel format is as specified in
* segment parameters. If NULL, a black frame is dumped. Needs to be held stable until fully read in MT mode.
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Can't write frame.
*/
void video(const void* framedata) throw(std::bad_alloc, std::runtime_error);
/**
* Is there frame being processed?
*/
bool is_frame_processing() throw();
/**
* Wait until frame has processed.
*/
void wait_frame_processing() throw();
/**
* Dump audio.
*
* Parameter audio: Audio, first to last channel, first to last sample order.
* Parameter samples: Number of samples to add. Note: Number of samples, not number of channels*samples.
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Can't write sound data.
*/
void audio(const short* audio, size_t samples) throw(std::bad_alloc, std::runtime_error);
/**
* Dump audio (stereo).
*
* Parameter laudio: Audio for left channel.
* Parameter raudio: Audio for right channel.
* Parameter samples: Number of samples to add.
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Can't write sound data or not stereo sound.
*/
void audio(const short* laudio, const short* raudio, size_t samples) throw(std::bad_alloc, std::runtime_error);
/**
* Signal end of dump.
*
* Throws std::bad_alloc: Not enough memory.
* Throws std::runtime_error: Can't flush the last segment.
*/
void end() throw(std::bad_alloc, std::runtime_error);
int encode_thread();
void set_capture_error(const std::string& err);
private:
//Information about buffered frame.
struct buffered_frame
{
std::vector<unsigned char> data;
bool keyframe;
unsigned compression_level;
bool forcebreak;
unsigned long fps_n;
unsigned long fps_d;
unsigned width;
unsigned height;
};
//Global parameters.
std::string dump_prefix;
unsigned long gp_sampling_rate;
//Current segment parameters.
unsigned long sp_fps_n;
unsigned long sp_fps_d;
enum pixelformat sp_dataformat;
unsigned sp_width;
unsigned sp_height;
size_t sp_stride;
unsigned sp_keyframe_distance;
unsigned sp_deflate_level;
unsigned long sp_max_segment_frames;
//Next segment parameters (some parameters can switch immediately).
bool switch_segments_on_next_frame;
unsigned spn_fps_n;
unsigned spn_fps_d;
enum pixelformat spn_dataformat;
unsigned spn_width;
unsigned spn_height;
//Current segment.
unsigned current_major_segment;
unsigned next_minor_segment;
unsigned long current_major_segment_frames;
unsigned frames_since_last_keyframe;
unsigned long frame_period_counter;
std::vector<unsigned char> previous_frame;
std::vector<unsigned char> compression_input;
std::vector<unsigned char> compression_output;
avi_file_structure* avifile_structure;
std::ofstream avifile;
//Sound&frame buffer.
std::vector<unsigned short> sound_buffer;
size_t buffered_sound_samples;
std::list<buffered_frame> frame_buffer;
//Fills compression_output with frame/sound packet and returns the total size.
size_t emit_frame(const std::vector<unsigned char>& data, bool keyframe, unsigned level);
size_t emit_sound(size_t samples);
//Write a frame/sound packet in compression_output into stream.
void emit_frame_stream(size_t size, bool keyframe);
void emit_sound_stream(size_t size, size_t samples);
//Read next frame from queue with associated sound of given length. Also handles splits.
void write_frame_av(size_t samples);
size_t samples_for_next_frame();
bool restart_segment_if_needed(bool force_break);
void flush_buffers(bool forced);
void start_segment(unsigned major_seg, unsigned minor_seg);
void end_segment();
void request_flush_buffers(bool forced);
void _video(const void* framedata);
//Multithreading stuff.
thread_class* frame_thread;
cv_class frame_cond;
mutex_class frame_mutex;
volatile bool quit_requested;
volatile bool flush_requested;
volatile bool flush_requested_forced;
volatile bool frame_processing;
volatile const void* frame_pointer;
volatile bool exception_error_present;
std::string exception_error;
};
#endif

118
src/library/workthread.cpp Normal file
View file

@ -0,0 +1,118 @@
#include "library/workthread.hpp"
#include <stdexcept>
struct worker_thread_reflector
{
int operator()(worker_thread* x)
{
(*x)(42);
}
};
worker_thread::worker_thread()
{
thread = NULL;
reflector = NULL;
workflag = 0;
busy = false;
exception_caught = false;
exception_oom = false;
joined = false;
}
worker_thread::~worker_thread()
{
set_workflag(WORKFLAG_QUIT_REQUEST);
if(!joined && thread)
thread->join();
delete thread;
delete reflector;
}
void worker_thread::request_quit()
{
{
//If the thread isn't there yet, wait for it.
umutex_class h(mutex);
if(!thread)
condition.wait(h);
}
set_workflag(WORKFLAG_QUIT_REQUEST);
if(!joined)
thread->join();
joined = true;
}
void worker_thread::set_busy()
{
busy = true;
}
void worker_thread::clear_busy()
{
umutex_class h(mutex);
busy = false;
condition.notify_all();
}
void worker_thread::wait_busy()
{
umutex_class h(mutex);
while(busy)
condition.wait(h);
}
void worker_thread::rethrow()
{
if(exception_caught) {
if(exception_oom)
throw std::bad_alloc();
else
throw std::runtime_error(exception_text);
}
}
void worker_thread::set_workflag(uint32_t flag)
{
umutex_class h(mutex);
workflag |= flag;
condition.notify_all();
}
uint32_t worker_thread::clear_workflag(uint32_t flag)
{
umutex_class h(mutex);
uint32_t tmp = workflag;
workflag &= ~flag;
return tmp;
}
uint32_t worker_thread::wait_workflag()
{
umutex_class h(mutex);
while(!workflag)
condition.wait(h);
return workflag;
}
int worker_thread::operator()(int dummy)
{
try {
entry();
} catch(std::bad_alloc& e) {
exception_oom = true;
exception_caught = true;
return 1;
} catch(std::exception& e) {
exception_text = e.what();
exception_caught = true;
return 1;
}
return 0;
}
void worker_thread::fire()
{
reflector = new worker_thread_reflector;
thread = new thread_class(*reflector, this);
}

View file

@ -1,35 +1,27 @@
OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp))
OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp)) avi/__all__.o
#Threads
ifdef THREADS
ifeq ($(THREADS), NATIVE)
VIDEO_CFLAGS += -DNATIVE_THREADS
else
ifeq ($(THREADS), BOOST)
VIDEO_CFLAGS += -DBOOST_THREADS
ifdef BOOST_THREAD_LIB
VIDEO_LDFLAGS += -l$(BOOST_THREAD_LIB)
else
VIDEO_LDFLAGS += -lboost_thread-mt
endif
else
$(error "Bad value for THREADS (expected NATIVE or BOOST)")
endif
endif
endif
.PRECIOUS: %.$(OBJECT_SUFFIX)
export VIDEO_CFLAGS
__all__.$(OBJECT_SUFFIX): $(OBJECTS)
$(REALLD) -r -o $@ $^
echo $(VIDEO_LDFLAGS) >__all__.ldflags
touch __all__.ldflags
avi/__all__.$(OBJECT_SUFFIX): forcelook
$(MAKE) -C avi
%.$(OBJECT_SUFFIX): %.cpp
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../include $(VIDEO_CLFAGS)
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../include
precheck:
forcelook:
@true
precheck:
$(MAKE) -C avi precheck
clean:
$(MAKE) -C avi clean
rm -f *.$(OBJECT_SUFFIX) *.ldflags

354
src/video/avi.cpp Normal file
View file

@ -0,0 +1,354 @@
#include "video/sox.hpp"
#include "video/avi/writer.hpp"
#include "video/avi/codec.hpp"
#include "core/advdumper.hpp"
#include "core/dispatch.hpp"
#include "lua/lua.hpp"
#include "core/misc.hpp"
#include "core/settings.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>
#include <cmath>
#include <sstream>
#include <zlib.h>
#define CSCD_PCM "cscd/pcm"
#define UNCOMPRESSED_PCM "uncompressed/pcm"
namespace
{
uint32_t rates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
128000, 176400, 192000};
uint32_t get_rate(uint32_t n, uint32_t d, unsigned mode)
{
if(mode == 0) {
unsigned bestidx = 0;
double besterror = 1e99;
for(size_t i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
double error = fabs(log(static_cast<double>(d) * rates[i] / n));
if(error < besterror) {
besterror = error;
bestidx = i;
}
}
return rates[bestidx];
} else if(mode == 1) {
return static_cast<uint32_t>(n / d);
} else if(mode == 2) {
return static_cast<uint32_t>((n + d - 1) / d);
} else if(mode == 3) {
uint32_t x = n;
uint32_t y = d;
while(y) {
uint32_t t = x % d;
x = y;
y = t;
}
return static_cast<uint32_t>(n / x);
}
}
boolean_setting dump_large("avi-large", false);
numeric_setting dtb("avi-top-border", 0, 8191, 0);
numeric_setting dbb("avi-bottom-border", 0, 8191, 0);
numeric_setting dlb("avi-left-border", 0, 8191, 0);
numeric_setting drb("avi-right-border", 0, 8191, 0);
numeric_setting max_frames_per_segment("avi-maxframes", 0, 999999999, 0);
numeric_setting soundrate_setting("avi-soundrate", 0, 3, 0);
std::pair<avi_video_codec_type*, avi_audio_codec_type*> find_codecs(const std::string& mode)
{
avi_video_codec_type* v = NULL;
avi_audio_codec_type* a = NULL;
std::string _mode = mode;
size_t s = _mode.find_first_of("/");
if(s < _mode.length()) {
std::string vcodec = _mode.substr(0, s);
std::string acodec = _mode.substr(s + 1);
v = avi_video_codec_type::find(vcodec);
a = avi_audio_codec_type::find(acodec);
}
return std::make_pair(v, a);
}
struct avi_info
{
std::string prefix;
struct avi_video_codec* vcodec;
struct avi_audio_codec* acodec;
uint32_t sample_rate;
uint16_t audio_chans;
uint32_t max_frames;
};
struct avi_worker : public worker_thread
{
avi_worker(const struct avi_info& info);
void entry();
void queue_video(uint32_t* _frame, uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d);
void queue_audio(int16_t* data, size_t samples);
private:
avi_writer aviout;
uint32_t* frame;
uint32_t frame_width;
uint32_t frame_height;
uint32_t frame_fps_n;
uint32_t frame_fps_d;
uint32_t segframes;
uint32_t max_segframes;
bool closed;
};
#define WORKFLAG_QUEUE_FRAME 1
#define WORKFLAG_FLUSH 2
#define WORKFLAG_END 4
avi_worker::avi_worker(const struct avi_info& info)
: aviout(info.prefix, *info.vcodec, *info.acodec, info.sample_rate, info.audio_chans)
{
segframes = 0;
max_segframes = info.max_frames;
fire();
}
void avi_worker::queue_video(uint32_t* _frame, uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d)
{
rethrow();
wait_busy();
frame = _frame;
frame_width = width;
frame_height = height;
frame_fps_n = fps_n;
frame_fps_d = fps_d;
set_busy();
set_workflag(WORKFLAG_QUEUE_FRAME);
}
void avi_worker::queue_audio(int16_t* data, size_t samples)
{
rethrow();
aviout.audio_queue().push(data, samples);
set_workflag(WORKFLAG_FLUSH);
}
void avi_worker::entry()
{
while(1) {
wait_workflag();
uint32_t work = clear_workflag(~WORKFLAG_QUIT_REQUEST);
//Flush the queue first in order to provode backpressure.
if(work & WORKFLAG_FLUSH) {
clear_workflag(WORKFLAG_FLUSH);
aviout.flush();
}
//Then add frames if any.
if(work & WORKFLAG_QUEUE_FRAME) {
frame_object f;
f.data = new uint32_t[frame_width * frame_height];
f.width = frame_width;
f.height = frame_height;
f.fps_n = frame_fps_n;
f.fps_d = frame_fps_d;
f.force_break = (segframes == max_segframes && max_segframes > 0);
if(f.force_break)
segframes = 0;
memcpy(&f.data[0], frame, 4 * frame_width * frame_height);
frame = NULL;
clear_workflag(WORKFLAG_QUEUE_FRAME);
clear_busy();
aviout.video_queue().push_back(f);
segframes++;
set_workflag(WORKFLAG_FLUSH);
}
//End the streaam if that is flagged.
if(work & WORKFLAG_END) {
if(!closed)
aviout.close();
closed = true;
clear_workflag(WORKFLAG_END | WORKFLAG_FLUSH | WORKFLAG_QUEUE_FRAME);
}
//If signaled to quit and no more work, do so.
if(work == WORKFLAG_QUIT_REQUEST) {
if(!closed)
aviout.close();
closed = true;
break;
}
}
}
void waitfn();
class avi_avsnoop : public information_dispatch
{
public:
avi_avsnoop(avi_info& info) throw(std::bad_alloc)
: information_dispatch("dump-avi-int")
{
enable_send_sound();
info.audio_chans = 2;
soundrate = get_sound_rate();
audio_record_rate = info.sample_rate = get_rate(soundrate.first, soundrate.second,
soundrate_setting);
worker = new avi_worker(info);
soxdumper = new sox_dumper(info.prefix + ".sox", static_cast<double>(soundrate.first) /
soundrate.second, 2);
dcounter = 0;
have_dumped_frame = false;
}
~avi_avsnoop() throw()
{
delete worker;
delete soxdumper;
}
void on_frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d)
{
uint32_t hscl = 1;
uint32_t vscl = 1;
if(dump_large && _frame.width < 400)
hscl = 2;
if(dump_large && _frame.height < 400)
vscl = 2;
render_video_hud(dscr, _frame, hscl, vscl, 0, 8, 16, dlb, dtb, drb, dbb, waitfn);
worker->queue_video(dscr.memory, dscr.width, dscr.height, fps_n, fps_d);
have_dumped_frame = true;
}
void on_sample(short l, short r)
{
short x[2];
x[0] = l;
x[1] = r;
dcounter += soundrate.first;
while(dcounter < soundrate.second * audio_record_rate + soundrate.first) {
if(have_dumped_frame)
worker->queue_audio(x, 2);
dcounter += soundrate.first;
}
dcounter -= (soundrate.second * audio_record_rate + soundrate.first);
if(have_dumped_frame)
soxdumper->sample(l, r);
}
void on_dump_end()
{
worker->request_quit();
soxdumper->close();
}
bool get_dumper_flag() throw()
{
return true;
}
avi_worker* worker;
private:
sox_dumper* soxdumper;
screen dscr;
unsigned dcounter;
bool have_dumped_frame;
std::pair<uint32_t, uint32_t> soundrate;
uint32_t audio_record_rate;
};
avi_avsnoop* vid_dumper;
void waitfn()
{
vid_dumper->worker->wait_busy();
}
class adv_avi_dumper : public adv_dumper
{
public:
adv_avi_dumper() : adv_dumper("INTERNAL-AVI") {information_dispatch::do_dumper_update(); }
~adv_avi_dumper() throw();
std::set<std::string> list_submodes() throw(std::bad_alloc)
{
std::set<std::string> x;
for(auto v = avi_video_codec_type::find_next(NULL); v; v = avi_video_codec_type::find_next(v))
for(auto a = avi_audio_codec_type::find_next(NULL); a;
a = avi_audio_codec_type::find_next(a))
x.insert(v->get_iname() + std::string("/") + a->get_iname());
return x;
}
bool wants_prefix(const std::string& mode) throw()
{
return true;
}
std::string name() throw(std::bad_alloc)
{
return "AVI (internal)";
}
std::string modename(const std::string& mode) throw(std::bad_alloc)
{
auto c = find_codecs(mode);
return c.first->get_hname() + std::string(" / ") + c.second->get_hname();
}
bool busy()
{
return (vid_dumper != NULL);
}
void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc,
std::runtime_error)
{
if(prefix == "")
throw std::runtime_error("Expected prefix");
if(vid_dumper)
throw std::runtime_error("AVI dumping already in progress");
struct avi_info info;
info.audio_chans = 2;
info.sample_rate = 32000;
info.max_frames = max_frames_per_segment;
info.prefix = prefix;
auto c = find_codecs(mode);
info.vcodec = c.first->get_instance();
info.acodec = c.second->get_instance();
try {
vid_dumper = new avi_avsnoop(info);
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
std::ostringstream x;
x << "Error starting AVI dump: " << e.what();
throw std::runtime_error(x.str());
}
messages << "Dumping AVI (" << c.first->get_hname() << " / " << c.second->get_hname()
<< ") to " << prefix << std::endl;
information_dispatch::do_dumper_update();
}
void end() throw()
{
if(!vid_dumper)
throw std::runtime_error("No AVI video dump in progress");
try {
vid_dumper->on_dump_end();
messages << "AVI Dump finished" << std::endl;
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
messages << "Error ending AVI dump: " << e.what() << std::endl;
}
delete vid_dumper;
vid_dumper = NULL;
information_dispatch::do_dumper_update();
}
} adv;
adv_avi_dumper::~adv_avi_dumper() throw()
{
}
}

22
src/video/avi/Makefile Normal file
View file

@ -0,0 +1,22 @@
OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp)) codec/__all__.o
.PRECIOUS: %.$(OBJECT_SUFFIX)
__all__.o: $(OBJECTS)
$(REALLD) -r -o $@ $^
codec/__all__.o: forcelook
$(MAKE) -C codec
%.$(OBJECT_SUFFIX): %.cpp
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../../include $(VIDEO_CLFAGS)
forcelook:
@true
precheck:
$(MAKE) -C codec precheck
clean:
$(MAKE) -C codec clean
rm -f *.$(OBJECT_SUFFIX) *.ldflags

319
src/video/avi/codec.cpp Normal file
View file

@ -0,0 +1,319 @@
#include "video/avi/codec.hpp"
#include "core/dispatch.hpp"
#include "core/misc.hpp"
#include "library/serialization.hpp"
avi_video_codec::~avi_video_codec() {};
avi_audio_codec::~avi_audio_codec() {};
avi_video_codec::format::format(uint32_t _compression, uint16_t bitcount)
{
suggested_buffer_size = 1000000;
max_bytes_per_sec = 10000000;
planes = 1;
bit_count = bitcount;
compression = _compression;
resolution_x = 4000;
resolution_y = 4000;
quality = 9999;
clr_used = 0;
clr_important = 0;
}
avi_audio_codec::format::format(uint16_t tag)
{
max_bytes_per_sec = 200000;
suggested_buffer_size = 16384;
format_tag = tag;
average_rate = 176400;
alignment = 4;
bitdepth = 16;
quality = 9999;
}
uint32_t get_actual_packet_type(uint8_t trackid, uint16_t typecode)
{
uint32_t t1 = trackid / 10 + 48;
uint32_t t2 = trackid % 10 + 48;
return t1 | t2 << 8 | static_cast<uint32_t>(typecode) << 16;
}
void fill_avi_structure(header_list& avih, avi_video_codec& vcodec, avi_audio_codec& acodec, uint32_t width,
uint32_t height, uint32_t fps_n, uint32_t fps_d, uint32_t samplerate, uint16_t channels)
{
avi_audio_codec::format afmt = acodec.reset(samplerate, channels);
avi_video_codec::format vfmt = vcodec.reset(width, height, fps_n, fps_d);
avih.avih.microsec_per_frame = (uint64_t)1000000 * fps_d / fps_n;
avih.avih.max_bytes_per_sec = afmt.max_bytes_per_sec + vfmt.max_bytes_per_sec;
avih.avih.padding_granularity = 0;
avih.avih.flags = 2064; //Trust chunk types, has index.
avih.avih.initial_frames = 0;
avih.avih.suggested_buffer_size = 1048576; //Just some value.
avih.videotrack.strh.handler = 0;
avih.videotrack.strh.flags = 0;
avih.videotrack.strh.priority = 0;
avih.videotrack.strh.language = 0;
avih.videotrack.strh.initial_frames = 0;
avih.videotrack.strh.start = 0;
avih.videotrack.strh.suggested_buffer_size = vfmt.suggested_buffer_size;
avih.videotrack.strh.quality = vfmt.quality;
avih.videotrack.strf.width = width;
avih.videotrack.strf.height = height;
avih.videotrack.strf.planes = vfmt.planes;
avih.videotrack.strf.bit_count = vfmt.bit_count;
avih.videotrack.strf.compression = vfmt.compression;
avih.videotrack.strf.size_image = (vfmt.bit_count + 7) / 8 * width * height;
avih.videotrack.strf.resolution_x = vfmt.resolution_x;
avih.videotrack.strf.resolution_y = vfmt.resolution_y;
avih.videotrack.strf.clr_used = vfmt.clr_used;
avih.videotrack.strf.clr_important = vfmt.clr_important;
avih.videotrack.strf.fps_n = fps_n;
avih.videotrack.strf.fps_d = fps_d;
avih.audiotrack.strh.handler = 0;
avih.audiotrack.strh.flags = 0;
avih.audiotrack.strh.priority = 0;
avih.audiotrack.strh.language = 0;
avih.audiotrack.strh.initial_frames = 0;
avih.audiotrack.strh.start = 0;
avih.audiotrack.strh.suggested_buffer_size = afmt.suggested_buffer_size;
avih.audiotrack.strh.quality = afmt.quality;
avih.audiotrack.strf.format_tag = afmt.format_tag;
avih.audiotrack.strf.channels = channels;
avih.audiotrack.strf.samples_per_second = samplerate;
avih.audiotrack.strf.average_bytes_per_second = afmt.average_rate;
avih.audiotrack.strf.block_align = afmt.alignment;
avih.audiotrack.strf.bits_per_sample = afmt.bitdepth;
avih.audiotrack.strf.blocksize = channels * (afmt.bitdepth + 7) / 8;
}
#define PADGRANULARITY 2
namespace
{
void write_pkt(struct avi_file_structure& avifile, const avi_packet& pkt, uint8_t track)
{
uint32_t fulltype = get_actual_packet_type(track, pkt.typecode);
char buf[8 + PADGRANULARITY];
write32ule(buf + 0, fulltype);
write32ule(buf + 4, pkt.payload.size());
write32ule(buf + 8, 0);
size_t padding = (PADGRANULARITY - pkt.payload.size() % PADGRANULARITY) % PADGRANULARITY;
avifile.outstream->write(buf, 8);
avifile.outstream->write(&pkt.payload[0], pkt.payload.size());
avifile.outstream->write(buf + 8, padding);
if(!*avifile.outstream)
throw std::runtime_error("Can't write AVI packet");
if(!pkt.hidden)
avifile.idx1.add_entry(index_entry(fulltype, pkt.indexflags, avifile.movi.payload_size + 4,
pkt.payload.size()));
avifile.movi.payload_size += (pkt.payload.size() + 8 + padding);
}
}
avi_output_stream::avi_output_stream()
: video_timer(60), audio_timer(60)
{
in_segment = false;
}
avi_output_stream::~avi_output_stream()
{
try {
if(in_segment)
end();
} catch(...) {
}
}
void avi_output_stream::start(std::ostream& out, avi_video_codec& _vcodec, avi_audio_codec& _acodec, uint32_t width,
uint32_t height, uint32_t fps_n, uint32_t fps_d, uint32_t samplerate, uint16_t channels)
{
if(in_segment)
end();
in_segment = false;
avi_audio_codec::format afmt = _acodec.reset(samplerate, channels);
avi_video_codec::format vfmt = _vcodec.reset(width, height, fps_n, fps_d);
header_list avih;
avih.avih.microsec_per_frame = (uint64_t)1000000 * fps_d / fps_n;
avih.avih.max_bytes_per_sec = afmt.max_bytes_per_sec + vfmt.max_bytes_per_sec;
avih.avih.padding_granularity = 0;
avih.avih.flags = 2064; //Trust chunk types, has index.
avih.avih.initial_frames = 0;
avih.avih.suggested_buffer_size = 1048576; //Just some value.
avih.videotrack.strh.handler = 0;
avih.videotrack.strh.flags = 0;
avih.videotrack.strh.priority = 0;
avih.videotrack.strh.language = 0;
avih.videotrack.strh.initial_frames = 0;
avih.videotrack.strh.start = 0;
avih.videotrack.strh.suggested_buffer_size = vfmt.suggested_buffer_size;
avih.videotrack.strh.quality = vfmt.quality;
avih.videotrack.strf.width = width;
avih.videotrack.strf.height = height;
avih.videotrack.strf.planes = vfmt.planes;
avih.videotrack.strf.bit_count = vfmt.bit_count;
avih.videotrack.strf.compression = vfmt.compression;
avih.videotrack.strf.size_image = (vfmt.bit_count + 7) / 8 * width * height;
avih.videotrack.strf.resolution_x = vfmt.resolution_x;
avih.videotrack.strf.resolution_y = vfmt.resolution_y;
avih.videotrack.strf.clr_used = vfmt.clr_used;
avih.videotrack.strf.clr_important = vfmt.clr_important;
avih.videotrack.strf.fps_n = fps_n;
avih.videotrack.strf.fps_d = fps_d;
avih.audiotrack.strh.handler = 0;
avih.audiotrack.strh.flags = 0;
avih.audiotrack.strh.priority = 0;
avih.audiotrack.strh.language = 0;
avih.audiotrack.strh.initial_frames = 0;
avih.audiotrack.strh.start = 0;
avih.audiotrack.strh.suggested_buffer_size = afmt.suggested_buffer_size;
avih.audiotrack.strh.quality = afmt.quality;
avih.audiotrack.strf.format_tag = afmt.format_tag;
avih.audiotrack.strf.channels = channels;
avih.audiotrack.strf.samples_per_second = samplerate;
avih.audiotrack.strf.average_bytes_per_second = afmt.average_rate;
avih.audiotrack.strf.block_align = afmt.alignment;
avih.audiotrack.strf.bits_per_sample = afmt.bitdepth;
avih.audiotrack.strf.blocksize = channels * ((afmt.bitdepth + 7) / 8);
avifile.hdrl = avih;
avifile.start_data(out);
acodec = &_acodec;
vcodec = &_vcodec;
achans = channels;
video_timer.rate(fps_n, fps_d);
audio_timer.rate(samplerate);
while(!vcodec->ready())
write_pkt(avifile, vcodec->getpacket(), 0);
while(!acodec->ready())
write_pkt(avifile, acodec->getpacket(), 1);
in_segment = true;
}
void avi_output_stream::frame(uint32_t* frame)
{
if(!in_segment)
throw std::runtime_error("Trying to write to non-open AVI");
vcodec->frame(frame);
while(!vcodec->ready())
write_pkt(avifile, vcodec->getpacket(), 0);
avifile.hdrl.videotrack.strh.add_frames(1);
}
void avi_output_stream::samples(int16_t* samples, size_t samplecount)
{
if(!in_segment)
throw std::runtime_error("Trying to write to non-open AVI");
acodec->samples(samples, samplecount);
while(!acodec->ready())
write_pkt(avifile, acodec->getpacket(), 1);
avifile.hdrl.audiotrack.strh.add_frames(samplecount);
for(size_t i = 0; i < samplecount; i++)
audio_timer.increment();
}
void avi_output_stream::end()
{
std::ostream& out = *avifile.outstream;
avifile.finish_avi();
in_segment = false;
}
size_t avi_output_stream::framesamples()
{
uint64_t next_frame_at = video_timer.read_next();
timer tmp_audio_timer = audio_timer;
size_t samples = 0;
while(tmp_audio_timer.read() < next_frame_at) {
tmp_audio_timer.increment();
samples++;
}
return samples;
}
uint64_t avi_output_stream::get_size_estimate()
{
if(!in_segment)
return 0;
return avifile.movi.payload_size;
}
bool avi_output_stream::readqueue(uint32_t* _frame, sample_queue& aqueue, bool force)
{
if(!in_segment)
throw std::runtime_error("Trying to write to non-open AVI");
size_t fsamples = framesamples();
if(!force && aqueue.available() < fsamples)
return false;
std::vector<int16_t> tmp;
tmp.resize(fsamples * achans);
aqueue.pull(&tmp[0], tmp.size());
frame(_frame);
video_timer.increment();
samples(&tmp[0], fsamples);
delete[] _frame;
return true;
}
template<typename T>
avi_codec_type<T>::avi_codec_type(const char* _iname, const char* _hname, T* (*_instance)())
{
iname = _iname;
hname = _hname;
instance = _instance;
codecs()[iname] = this;
//Make UI rereread available dumpers.
if(!in_global_ctors())
information_dispatch::do_dumper_update();
}
template<typename T>
avi_codec_type<T>::~avi_codec_type()
{
codecs().erase(iname);
//Make UI rereread available dumpers.
if(!in_global_ctors())
information_dispatch::do_dumper_update();
}
template<typename T>
avi_codec_type<T>* avi_codec_type<T>::find(const std::string& iname)
{
if(!codecs().count(iname))
return NULL;
return codecs()[iname];
}
template<typename T>
avi_codec_type<T>* avi_codec_type<T>::find_next(avi_codec_type<T>* type)
{
typename std::map<std::string, avi_codec_type<T>*>::iterator i;
if(!type)
i = codecs().lower_bound("");
else
i = codecs().upper_bound(type->iname);
if(i == codecs().end())
return NULL;
else
return i->second;
}
template<typename T> std::string avi_codec_type<T>::get_iname() { return iname; }
template<typename T> std::string avi_codec_type<T>::get_hname() { return hname; }
template<typename T> T* avi_codec_type<T>::get_instance() { return instance(); }
template<typename T> std::map<std::string, avi_codec_type<T>*>& avi_codec_type<T>::codecs()
{
static std::map<std::string, avi_codec_type<T>*> x;
return x;
}
template struct avi_codec_type<avi_video_codec>;
template struct avi_codec_type<avi_audio_codec>;

View file

@ -0,0 +1,27 @@
OBJECTS=video/__all__.$(OBJECT_SUFFIX) audio/__all__.$(OBJECT_SUFFIX)
.PRECIOUS: %.$(OBJECT_SUFFIX)
__all__.$(OBJECT_SUFFIX): $(OBJECTS)
$(REALLD) -r -o $@ $^
audio/__all__.$(OBJECT_SUFFIX): forcelook
$(MAKE) -C audio
video/__all__.$(OBJECT_SUFFIX): forcelook
$(MAKE) -C video
%.$(OBJECT_SUFFIX): %.cpp
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../include $(VIDEO_CLFAGS)
forcelook:
@true
precheck:
$(MAKE) -C audio precheck
$(MAKE) -C video precheck
clean:
$(MAKE) -C audio clean
$(MAKE) -C video clean
rm -f *.$(OBJECT_SUFFIX) *.ldflags

View file

@ -0,0 +1,17 @@
OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp))
.PRECIOUS: %.$(OBJECT_SUFFIX)
export VIDEO_CFLAGS
__all__.o: $(OBJECTS)
$(REALLD) -r -o $@ $^
%.$(OBJECT_SUFFIX): %.cpp
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../../../../include $(VIDEO_CLFAGS)
precheck:
@true
clean:
rm -f *.$(OBJECT_SUFFIX) *.ldflags

View file

@ -0,0 +1,57 @@
#include "video/avi/codec.hpp"
#include "library/serialization.hpp"
namespace
{
struct avi_codec_pcm : public avi_audio_codec
{
~avi_codec_pcm();
avi_audio_codec::format reset(uint32_t samplerate, uint16_t channels);
void samples(int16_t* data, size_t samples);
bool ready();
avi_packet getpacket();
private:
uint16_t chans;
avi_packet out;
bool ready_flag;
};
avi_codec_pcm::~avi_codec_pcm()
{
}
avi_audio_codec::format avi_codec_pcm::reset(uint32_t samplerate, uint16_t channels)
{
chans = channels;
ready_flag = true;
avi_audio_codec::format fmt(1); //1 => PCM.
fmt.max_bytes_per_sec = fmt.average_rate = samplerate * channels * 2;
fmt.alignment = channels * 2;
fmt.bitdepth = 16;
return fmt;
}
void avi_codec_pcm::samples(int16_t* data, size_t samples)
{
out.payload.resize(2 * chans * samples);
for(size_t i = 0; i < chans * samples; i++)
write16sle(&out.payload[2 * i], data[i]);
out.typecode = 0x6277;
out.indexflags = 0x10;
out.hidden = false;
ready_flag = false;
}
bool avi_codec_pcm::ready()
{
return ready_flag;
}
avi_packet avi_codec_pcm::getpacket()
{
ready_flag = true;
return out;
}
avi_audio_codec_type pcm("pcm", "PCM audio", []() -> avi_audio_codec* { return new avi_codec_pcm;});
}

View file

@ -0,0 +1,17 @@
OBJECTS=$(patsubst %.cpp,%.$(OBJECT_SUFFIX),$(wildcard *.cpp))
.PRECIOUS: %.$(OBJECT_SUFFIX)
export VIDEO_CFLAGS
__all__.o: $(OBJECTS)
$(REALLD) -r -o $@ $^
%.$(OBJECT_SUFFIX): %.cpp
$(REALCC) $(CFLAGS) -c -o $@ $< -I../../../../../include $(VIDEO_CLFAGS)
precheck:
@true
clean:
rm -f *.$(OBJECT_SUFFIX) *.ldflags

View file

@ -0,0 +1,180 @@
#include "video/avi/codec.hpp"
#include "core/settings.hpp"
#include <limits>
#include <cstring>
#include <cerrno>
#include <stdexcept>
#include <zlib.h>
#define CBUFFER 16384
namespace
{
numeric_setting clvl("avi-cscd-compression", 0, 9, 7);
numeric_setting kint("avi-cscd-keyint", 1, 999999999, 0);
struct avi_codec_cscd : public avi_video_codec
{
avi_codec_cscd(uint32_t _level, uint32_t maxpframes);
~avi_codec_cscd();
avi_video_codec::format reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d);
void frame(uint32_t* data);
bool ready();
avi_packet getpacket();
private:
void readrow(uint32_t* rptr);
avi_packet out;
bool ready_flag;
unsigned iwidth;
unsigned iheight;
unsigned ewidth;
unsigned eheight;
unsigned pframes;
unsigned max_pframes;
unsigned level;
std::vector<uint8_t> row;
std::vector<uint8_t> ctmp;
std::vector<uint8_t> prevframe;
};
avi_codec_cscd::~avi_codec_cscd()
{
}
avi_codec_cscd::avi_codec_cscd(uint32_t _level, uint32_t maxpframes)
{
if(_level < 0 || _level > 9)
throw std::runtime_error("Invalid compression level");
level = _level;
max_pframes = maxpframes;
ctmp.resize(CBUFFER);
}
avi_video_codec::format avi_codec_cscd::reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d)
{
pframes = std::numeric_limits<unsigned>::max(); //Next frame has to be keyframe.
iwidth = width;
iheight = height;
ewidth = (iwidth + 3) >> 2 << 2;
eheight = (iheight + 3) >> 2 << 2;
ready_flag = true;
row.resize(3 * ewidth);
prevframe.resize(3 * ewidth * eheight);
memset(&row[0], 0, 3 * ewidth);
memset(&prevframe[0], 0, 3 * ewidth * eheight);
avi_video_codec::format fmt(0x44435343, 24);
return fmt;
}
void avi_codec_cscd::frame(uint32_t* data)
{
z_stream zlib;
bool buffer_loaded = false;
memset(&zlib, 0, sizeof(zlib));
int r = deflateInit(&zlib, level);
switch(r) {
case Z_ERRNO:
throw std::runtime_error(strerror(errno));
case Z_STREAM_ERROR:
throw std::runtime_error("Illegal compression level");
case Z_DATA_ERROR:
throw std::runtime_error("Data error while initializing zlib state?");
case Z_MEM_ERROR:
throw std::bad_alloc();
case Z_BUF_ERROR:
throw std::runtime_error("Buffer error while initializing zlib state?");
case Z_VERSION_ERROR:
throw std::runtime_error("Zlib is FUBAR");
case Z_OK:
break;
default:
throw std::runtime_error("Unkonwn error from deflateInit");
};
bool keyframe = false;
if(pframes >= max_pframes) {
keyframe = true;
pframes = 0;
} else
pframes++;
out.payload.resize(2);
out.payload[0] = (keyframe ? 0x3 : 0x2) | (level << 4);
out.payload[1] = 8; //RGB24.
uint32_t iy = 0;
for(uint32_t y = eheight - 1; y < eheight; y--, iy++) {
bool done = true;
if(y >= iheight)
readrow(NULL);
else
readrow(data + y * iwidth);
if(keyframe) {
memcpy(&prevframe[3 * iy * ewidth], &row[0], 3 * ewidth);
} else {
//Ew, we need to have prevframe = row, row = row - prevframe at the same time.
for(unsigned i = 0; i < 3 * ewidth; i++) {
uint8_t tmp = row[i];
row[i] -= prevframe[3 * iy * ewidth + i];
prevframe[3 * iy * ewidth + i] = tmp;
}
}
zlib.next_in = &row[0];
zlib.avail_in = row.size();
if(y == 0)
done = false;
while(zlib.avail_in || !done) {
//Make space in output buffer.
if(!zlib.avail_out) {
if(buffer_loaded) {
size_t p = out.payload.size();
out.payload.resize(p + ctmp.size());
memcpy(&out.payload[p], &ctmp[0], ctmp.size());
}
zlib.next_out = &ctmp[0];
zlib.avail_out = ctmp.size();
buffer_loaded = true;
}
r = deflate(&zlib, (y == 0) ? Z_FINISH : 0);
if(r == Z_STREAM_END)
done = true;
}
}
if(buffer_loaded) {
size_t p = out.payload.size();
out.payload.resize(p + (ctmp.size() - zlib.avail_out));
memcpy(&out.payload[p], &ctmp[0], ctmp.size() - zlib.avail_out);
}
deflateEnd(&zlib);
out.typecode = 0x6264; //Not exactly correct according to specs...
out.hidden = false;
out.indexflags = keyframe ? 0x10 : 0;
ready_flag = false;
}
bool avi_codec_cscd::ready()
{
return ready_flag;
}
avi_packet avi_codec_cscd::getpacket()
{
ready_flag = true;
return out;
}
void avi_codec_cscd::readrow(uint32_t* rptr)
{
if(!rptr)
memset(&row[0], 0, 3 * iwidth);
else
for(uint32_t i = 0; i < iwidth; i++) {
row[3 * i + 0] = rptr[i] >> 16;
row[3 * i + 1] = rptr[i] >> 8;
row[3 * i + 2] = rptr[i] >> 0;
}
}
avi_video_codec_type rgb("cscd", "Camstudio video codec",
[]() -> avi_video_codec* { return new avi_codec_cscd(clvl, kint);});
}

View file

@ -0,0 +1,93 @@
#include "video/avi/codec.hpp"
#include <limits>
#include <cstring>
#include <cerrno>
#include <stdexcept>
#include <zlib.h>
#define CBUFFER 65536
namespace
{
struct avi_codec_uncompressed : public avi_video_codec
{
~avi_codec_uncompressed();
avi_video_codec::format reset(uint32_t width, uint32_t height, uint32_t fps_n, uint32_t fps_d);
void frame(uint32_t* data);
bool ready();
avi_packet getpacket();
private:
void readrow(uint32_t* rptr);
avi_packet out;
bool ready_flag;
unsigned iwidth;
unsigned iheight;
unsigned ewidth;
unsigned eheight;
std::vector<uint8_t> row;
};
avi_codec_uncompressed::~avi_codec_uncompressed()
{
}
avi_video_codec::format avi_codec_uncompressed::reset(uint32_t width, uint32_t height, uint32_t fps_n,
uint32_t fps_d)
{
iwidth = width;
iheight = height;
ewidth = (iwidth + 3) >> 2 << 2;
eheight = (iheight + 3) >> 2 << 2;
ready_flag = true;
row.resize(3 * ewidth);
memset(&row[0], 0, 3 * ewidth);
avi_video_codec::format fmt(0, 24); //Is 0 correct value for compression?
return fmt;
}
void avi_codec_uncompressed::frame(uint32_t* data)
{
out.payload.resize(3 * ewidth * eheight);
uint32_t s = 0;
for(uint32_t y = 0; y < eheight; y++) {
bool done = true;
if(y >= iheight)
readrow(NULL);
else
readrow(data + (iheight - y - 1) * iwidth);
memcpy(&out.payload[3 * ewidth * y], &row[0], 3 * ewidth);
}
out.typecode = 0x6264;
out.hidden = false;
out.indexflags = 0x10;
ready_flag = false;
}
bool avi_codec_uncompressed::ready()
{
return ready_flag;
}
avi_packet avi_codec_uncompressed::getpacket()
{
ready_flag = true;
return out;
}
void avi_codec_uncompressed::readrow(uint32_t* rptr)
{
if(!rptr)
memset(&row[0], 0, 3 * iwidth);
else
for(uint32_t i = 0; i < iwidth; i++) {
row[3 * i + 0] = rptr[i] >> 16;
row[3 * i + 1] = rptr[i] >> 8;
row[3 * i + 2] = rptr[i] >> 0;
}
}
avi_video_codec_type rgb("uncompressed", "Uncompressed video",
[]() -> avi_video_codec* { return new avi_codec_uncompressed;});
}

View file

@ -0,0 +1,68 @@
#include "video/avi/samplequeue.hpp"
#define BLOCKSIZE 4096
sample_queue::sample_queue()
{
rptr = wptr = 0;
size = 0;
blank = true;
}
void sample_queue::push(const int16_t* samples, size_t count)
{
umutex_class(mutex);
size_t dsize = available();
if(dsize + count > size) {
//Expand the buffer.
std::vector<int16_t> newbuffer;
newbuffer.resize((dsize + count + BLOCKSIZE - 1) / BLOCKSIZE * BLOCKSIZE);
size_t trptr = rptr;
for(size_t i = 0; i < dsize; i++) {
newbuffer[i] = data[trptr++];
if(trptr == size)
trptr = 0;
}
data.swap(newbuffer);
size = data.size();
rptr = 0;
wptr = dsize;
}
while(count) {
data[wptr++] = *samples;
if(wptr == size)
wptr = 0;
blank = false;
samples++;
count--;
}
}
void sample_queue::pull(int16_t* samples, size_t count)
{
umutex_class(mutex);
while(count) {
if(!blank) {
*samples = data[rptr++];
if(rptr == size)
rptr = 0;
if(rptr == wptr)
blank = true;
} else
*samples = 0;
samples++;
count--;
}
}
size_t sample_queue::available()
{
umutex_class(mutex);
if(blank)
return 0;
else if(rptr < wptr)
return wptr - rptr;
else
return size - (rptr - wptr);
}

View file

@ -1,4 +1,4 @@
#include "video/avi_structure.hpp"
#include "video/avi/structure.hpp"
#include "library/serialization.hpp"
#include <cstring>
@ -71,6 +71,7 @@ void stream_format_audio::serialize(std::ostream& out)
size_t stream_header::size() { return 72; }
stream_header::stream_header() { length = 0; }
void stream_header::reset() { length = 0; }
void stream_header::add_frames(size_t count) { length = length + count; }
void stream_header::serialize(std::ostream& out, struct stream_format_base& format)
@ -119,6 +120,9 @@ void stream_header_list<format>::serialize(std::ostream& out)
strf.serialize(out);
}
template<class format>
void stream_header_list<format>::reset() { strh.reset(); }
size_t avi_header::size() { return 64; }
void avi_header::serialize(std::ostream& out, stream_header_list<stream_format_video>& videotrack, uint32_t tracks)
{
@ -161,9 +165,16 @@ void header_list::serialize(std::ostream& out)
audiotrack.serialize(out);
}
void header_list::reset()
{
audiotrack.reset();
videotrack.reset();
}
size_t movi_chunk::write_offset() { return 12; }
size_t movi_chunk::size() { return 12 + payload_size; }
movi_chunk::movi_chunk() { payload_size = 0; }
void movi_chunk::reset() { payload_size = 0; }
void movi_chunk::add_payload(size_t s) { payload_size = payload_size + s; }
void movi_chunk::serialize(std::ostream& out)
{
@ -224,25 +235,32 @@ void idx1_chunk::serialize(std::ostream& out)
i->serialize(out);
}
void idx1_chunk::reset()
{
entries.clear();
}
size_t avi_file_structure::write_offset() { return 12 + hdrl.size() + movi.write_offset(); }
size_t avi_file_structure::size() { return 12 + hdrl.size() + movi.size() + idx1.size(); }
void avi_file_structure::serialize(std::ostream& out)
void avi_file_structure::serialize()
{
std::vector<char> buf;
buf.resize(12);
write32ule(&buf[0], 0x46464952UL); //RIFF.
write32ule(&buf[4], size() - 8);
write32ule(&buf[8], 0x20495641UL); //Type.
out.write(&buf[0], buf.size());
if(!out)
outstream->write(&buf[0], buf.size());
if(!*outstream)
throw std::runtime_error("Can't write AVI header");
hdrl.serialize(out);
movi.serialize(out);
idx1.serialize(out);
hdrl.serialize(*outstream);
movi.serialize(*outstream);
idx1.serialize(*outstream);
}
void avi_file_structure::start_data(std::ostream& out)
{
movi.reset();
idx1.reset();
out.seekp(0, std::ios_base::beg);
size_t reserved_for_header = write_offset();
std::vector<char> tmp;
@ -250,12 +268,14 @@ void avi_file_structure::start_data(std::ostream& out)
out.write(&tmp[0], tmp.size());
if(!out)
throw std::runtime_error("Can't write dummy header");
outstream = &out;
}
void avi_file_structure::finish_avi(std::ostream& out)
void avi_file_structure::finish_avi()
{
out.seekp(0, std::ios_base::beg);
serialize(out);
if(!out)
outstream->seekp(0, std::ios_base::beg);
serialize();
if(!*outstream)
throw std::runtime_error("Can't finish AVI");
}

49
src/video/avi/timer.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "video/avi/timer.hpp"
#include "library/minmax.hpp"
timer::timer(uint32_t rate_n, uint32_t rate_d)
{
w = n = 0;
set_step(rate_n, rate_d);
}
void timer::rate(uint32_t rate_n, uint32_t rate_d)
{
double old_d = d;
uint64_t old_d2 = d;
set_step(rate_n, rate_d);
//Adjust n.
if(d == old_d2)
return;
uint64_t n = n * (d / old_d) + 0.5;
w += (n / d);
n %= d;
}
// The highest value rate_n can safely have: 9,223,372,036,854,775,808
// The highest value rate_d can safely have: 18,446,744,073
void timer::set_step(uint32_t rate_n, uint32_t rate_d)
{
uint64_t maxnscl = 9223372036854775808ULL / rate_n;
uint64_t maxdscl = 18446744073ULL / rate_d;
uint64_t maxscl = min(maxnscl, maxdscl);
uint64_t _rate_n = maxscl * rate_n;
uint64_t _rate_d = maxscl * rate_d;
d = _rate_n;
sw = 1000000000ULL * _rate_d / _rate_n;
sn = 1000000000ULL * _rate_d % _rate_n;
}
uint64_t timer::read_next()
{
uint64_t tmp_w = w + sw;
uint64_t tmp_n = n + sn;
tmp_w += (tmp_n / d);
tmp_n %= d;
return tmp_w;
}
void timer::reset()
{
w = n = 0;
}

89
src/video/avi/writer.cpp Normal file
View file

@ -0,0 +1,89 @@
#include "video/avi/writer.hpp"
#include <sstream>
#include <iomanip>
avi_writer::~avi_writer()
{
try {
if(!closed)
close();
} catch(...) {
}
}
std::deque<frame_object>& avi_writer::video_queue()
{
return vqueue;
}
sample_queue& avi_writer::audio_queue()
{
return aqueue;
}
void avi_writer::flush()
{
flush(false);
}
void avi_writer::close()
{
flush(true);
aviout.end();
avifile.close();
closed = true;
curwidth = curheight = curfps_n = curfps_d = 0;
}
void avi_writer::flush(bool force)
{
if(vqueue.empty())
return;
bool sbreak = false;
if(closed)
sbreak = true; //Start first segment.
if(aviout.get_size_estimate() > 2100000000)
sbreak = true; //Break due to size.
struct frame_object& f = vqueue.front();
if(f.force_break)
sbreak = true; //Manual force break.
if(f.width != curwidth || f.height != curheight || f.fps_n != curfps_n || f.fps_d != curfps_d)
sbreak = true; //Break due resolution / rate change.
if(sbreak) {
if(!closed) {
aviout.end();
avifile.close();
closed = true;
}
curwidth = f.width;
curheight = f.height;
curfps_n = f.fps_n;
curfps_d = f.fps_d;
std::string aviname;
{
std::ostringstream x;
x << prefix << "_" << std::setw(5) << std::setfill('0') << next_segment << ".avi";
aviname = x.str();
}
avifile.open(aviname, std::ios::out | std::ios::binary);
if(!avifile)
throw std::runtime_error("Can't open '" + aviname + "'");
next_segment++;
aviout.start(avifile, vcodec, acodec, curwidth, curheight, curfps_n, curfps_d, samplerate,
channels);
closed = false;
}
if(aviout.readqueue(f.data, aqueue, force))
vqueue.pop_front();
}
avi_writer::avi_writer(const std::string& _prefix, struct avi_video_codec& _vcodec, struct avi_audio_codec& _acodec,
uint32_t _samplerate, uint16_t _audiochannels)
: vcodec(_vcodec), acodec(_acodec)
{
prefix = _prefix;
closed = true;
next_segment = 0;
samplerate = _samplerate;
channels = _audiochannels;
}

View file

@ -1,248 +0,0 @@
#include "video/cscd.hpp"
#include "video/sox.hpp"
#include "core/advdumper.hpp"
#include "core/dispatch.hpp"
#include "lua/lua.hpp"
#include "core/misc.hpp"
#include "core/settings.hpp"
#include <iomanip>
#include <cassert>
#include <cstring>
#include <cmath>
#include <sstream>
#include <zlib.h>
namespace
{
uint32_t rates[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
128000, 176400, 192000};
uint32_t get_rate(uint32_t n, uint32_t d, unsigned mode)
{
if(mode == 0) {
unsigned bestidx = 0;
double besterror = 1e99;
for(size_t i = 0; i < sizeof(rates) / sizeof(rates[0]); i++) {
double error = fabs(log(static_cast<double>(d) * rates[i] / n));
if(error < besterror) {
besterror = error;
bestidx = i;
}
}
return rates[bestidx];
} else if(mode == 1) {
return static_cast<uint32_t>(n / d);
} else if(mode == 2) {
return static_cast<uint32_t>((n + d - 1) / d);
}
}
struct avi_info
{
unsigned compression_level;
uint32_t audio_sampling_rate;
uint32_t keyframe_interval;
uint32_t max_frames_per_segment;
};
boolean_setting dump_large("avi-large", false);
numeric_setting dtb("avi-top-border", 0, 8191, 0);
numeric_setting dbb("avi-bottom-border", 0, 8191, 0);
numeric_setting dlb("avi-left-border", 0, 8191, 0);
numeric_setting drb("avi-right-border", 0, 8191, 0);
numeric_setting clevel("avi-compression", 0, 18, 7);
numeric_setting max_frames_per_segment("avi-maxframes", 0, 999999999, 0);
numeric_setting soundrate_setting("avi-soundrate", 0, 2, 0);
void waitfn();
class avi_avsnoop : public information_dispatch
{
public:
avi_avsnoop(const std::string& prefix, struct avi_info parameters) throw(std::bad_alloc)
: information_dispatch("dump-avi-cscd")
{
enable_send_sound();
_parameters = parameters;
avi_cscd_dumper::global_parameters gp;
avi_cscd_dumper::segment_parameters sp;
soundrate = get_sound_rate();
gp.sampling_rate = get_rate(soundrate.first, soundrate.second, soundrate_setting);
sp.fps_n = 60;
sp.fps_d = 1;
sp.dataformat = avi_cscd_dumper::PIXFMT_XRGB;
sp.width = 256;
sp.height = 224;
sp.default_stride = true;
sp.stride = 512;
sp.keyframe_distance = parameters.keyframe_interval;
sp.deflate_level = parameters.compression_level;
sp.max_segment_frames = parameters.max_frames_per_segment;
vid_dumper = new avi_cscd_dumper(prefix, gp, sp);
audio_record_rate = parameters.audio_sampling_rate;
soxdumper = new sox_dumper(prefix + ".sox", static_cast<double>(soundrate.first) /
soundrate.second, 2);
dcounter = 0;
have_dumped_frame = false;
}
~avi_avsnoop() throw()
{
delete vid_dumper;
delete soxdumper;
}
void on_frame(struct lcscreen& _frame, uint32_t fps_n, uint32_t fps_d)
{
uint32_t hscl = 1;
uint32_t vscl = 1;
if(dump_large && _frame.width < 400)
hscl = 2;
if(dump_large && _frame.height < 400)
vscl = 2;
render_video_hud(dscr, _frame, hscl, vscl, 16, 8, 0, dlb, dtb, drb, dbb, waitfn);
avi_cscd_dumper::segment_parameters sp;
sp.fps_n = fps_n;
sp.fps_d = fps_d;
uint32_t x = 0x18100800;
if(*reinterpret_cast<const uint8_t*>(&x) == 0x18)
sp.dataformat = avi_cscd_dumper::PIXFMT_XRGB;
else
sp.dataformat = avi_cscd_dumper::PIXFMT_BGRX;
sp.width = dscr.width;
sp.height = dscr.height;
sp.default_stride = true;
sp.stride = 1024;
sp.keyframe_distance = _parameters.keyframe_interval;
sp.deflate_level = _parameters.compression_level;
sp.max_segment_frames = _parameters.max_frames_per_segment;
vid_dumper->set_segment_parameters(sp);
vid_dumper->video(dscr.memory);
have_dumped_frame = true;
}
void on_sample(short l, short r)
{
dcounter += soundrate.first;
while(dcounter < soundrate.second * audio_record_rate + soundrate.first) {
if(have_dumped_frame)
vid_dumper->audio(&l, &r, 1);
dcounter += soundrate.first;
}
dcounter -= (soundrate.second * audio_record_rate + soundrate.first);
if(have_dumped_frame)
soxdumper->sample(l, r);
}
void on_dump_end()
{
vid_dumper->wait_frame_processing();
vid_dumper->end();
soxdumper->close();
}
bool get_dumper_flag() throw()
{
return true;
}
avi_cscd_dumper* vid_dumper;
private:
sox_dumper* soxdumper;
screen dscr;
unsigned dcounter;
struct avi_info _parameters;
bool have_dumped_frame;
std::pair<uint32_t, uint32_t> soundrate;
uint32_t audio_record_rate;
};
avi_avsnoop* vid_dumper;
void waitfn()
{
vid_dumper->vid_dumper->wait_frame_processing();
}
class adv_avi_dumper : public adv_dumper
{
public:
adv_avi_dumper() : adv_dumper("INTERNAL-AVI-CSCD") {information_dispatch::do_dumper_update(); }
~adv_avi_dumper() throw();
std::set<std::string> list_submodes() throw(std::bad_alloc)
{
std::set<std::string> x;
return x;
}
bool wants_prefix(const std::string& mode) throw()
{
return true;
}
std::string name() throw(std::bad_alloc)
{
return "AVI (internal CSCD)";
}
std::string modename(const std::string& mode) throw(std::bad_alloc)
{
return "";
}
bool busy()
{
return (vid_dumper != NULL);
}
void start(const std::string& mode, const std::string& prefix) throw(std::bad_alloc,
std::runtime_error)
{
if(prefix == "")
throw std::runtime_error("Expected prefix");
if(vid_dumper)
throw std::runtime_error("AVI(CSCD) dumping already in progress");
unsigned long level2 = (unsigned long)clevel;
struct avi_info parameters;
parameters.compression_level = (level2 > 9) ? (level2 - 9) : level2;
parameters.audio_sampling_rate = 32000;
parameters.keyframe_interval = (level2 > 9) ? 300 : 1;
parameters.max_frames_per_segment = max_frames_per_segment;
try {
vid_dumper = new avi_avsnoop(prefix, parameters);
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
std::ostringstream x;
x << "Error starting AVI(CSCD) dump: " << e.what();
throw std::runtime_error(x.str());
}
messages << "Dumping AVI(CSCD) to " << prefix << " at level " << level2 << std::endl;
information_dispatch::do_dumper_update();
}
void end() throw()
{
if(!vid_dumper)
throw std::runtime_error("No AVI(CSCD) video dump in progress");
try {
vid_dumper->on_dump_end();
messages << "AVI(CSCD) Dump finished" << std::endl;
} catch(std::bad_alloc& e) {
throw;
} catch(std::exception& e) {
messages << "Error ending AVI(CSCD) dump: " << e.what() << std::endl;
}
delete vid_dumper;
vid_dumper = NULL;
information_dispatch::do_dumper_update();
}
} adv;
adv_avi_dumper::~adv_avi_dumper() throw()
{
}
}

View file

@ -1,616 +0,0 @@
#include "video/cscd.hpp"
#include <zlib.h>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <vector>
#include <iostream>
#include <list>
#include "video/avi_structure.hpp"
#include "library/serialization.hpp"
#define AVI_CUTOFF_SIZE 2100000000
namespace
{
struct dumper_thread_obj
{
int operator()(avi_cscd_dumper* d)
{
try {
return d->encode_thread();
} catch(std::exception& e) {
std::cerr << "Encode thread threw: " << e.what() << std::endl;
d->set_capture_error(e.what());
}
return 1;
}
};
void copy_row(unsigned char* target, const unsigned char* src, unsigned width,
enum avi_cscd_dumper::pixelformat pf)
{
unsigned ewidth = (width + 3) >> 2 << 2;
for(unsigned i = 0; i < width; i++) {
switch(pf) {
case avi_cscd_dumper::PIXFMT_BGRX:
target[3 * i + 0] = src[4 * i + 0];
target[3 * i + 1] = src[4 * i + 1];
target[3 * i + 2] = src[4 * i + 2];
break;
case avi_cscd_dumper::PIXFMT_RGBX:
target[3 * i + 0] = src[4 * i + 2];
target[3 * i + 1] = src[4 * i + 1];
target[3 * i + 2] = src[4 * i + 0];
break;
case avi_cscd_dumper::PIXFMT_XRGB:
target[3 * i + 0] = src[4 * i + 3];
target[3 * i + 1] = src[4 * i + 2];
target[3 * i + 2] = src[4 * i + 1];
break;
case avi_cscd_dumper::PIXFMT_XBGR:
target[3 * i + 0] = src[4 * i + 1];
target[3 * i + 1] = src[4 * i + 2];
target[3 * i + 2] = src[4 * i + 3];
break;
}
}
memset(target + 3 * width, 0, 3 * (ewidth - width));
}
}
namespace
{
void fill_avi_structure(struct avi_file_structure* avis, unsigned width, unsigned height, unsigned long fps_n,
unsigned long fps_d, unsigned long sampling_rate)
{
avis->hdrl.avih.microsec_per_frame = (uint64_t)1000000 * fps_d / fps_n;
avis->hdrl.avih.max_bytes_per_sec = 1000000;
avis->hdrl.avih.padding_granularity = 0;
avis->hdrl.avih.flags = 2064;
avis->hdrl.avih.initial_frames = 0;
avis->hdrl.avih.suggested_buffer_size = 1000000;
avis->hdrl.videotrack.strh.handler = 0;
avis->hdrl.videotrack.strh.flags = 0;
avis->hdrl.videotrack.strh.priority = 0;
avis->hdrl.videotrack.strh.language = 0;
avis->hdrl.videotrack.strh.initial_frames = 0;
avis->hdrl.videotrack.strh.start = 0;
avis->hdrl.videotrack.strh.suggested_buffer_size = 1000000;
avis->hdrl.videotrack.strh.quality = 9999;
avis->hdrl.videotrack.strf.width = width;
avis->hdrl.videotrack.strf.height = height;
avis->hdrl.videotrack.strf.planes = 1;
avis->hdrl.videotrack.strf.bit_count = 24;
avis->hdrl.videotrack.strf.compression = 0x44435343;
avis->hdrl.videotrack.strf.size_image = (3UL * width * height);
avis->hdrl.videotrack.strf.resolution_x = 4000;
avis->hdrl.videotrack.strf.resolution_y = 4000;
avis->hdrl.videotrack.strf.clr_used = 0;
avis->hdrl.videotrack.strf.clr_important = 0;
avis->hdrl.videotrack.strf.fps_n = fps_n;
avis->hdrl.videotrack.strf.fps_d = fps_d;
avis->hdrl.audiotrack.strh.handler = 0;
avis->hdrl.audiotrack.strh.flags = 0;
avis->hdrl.audiotrack.strh.priority = 0;
avis->hdrl.audiotrack.strh.language = 0;
avis->hdrl.audiotrack.strh.initial_frames = 0;
avis->hdrl.audiotrack.strh.start = 0;
avis->hdrl.audiotrack.strh.suggested_buffer_size = 1000000;
avis->hdrl.audiotrack.strh.quality = 9999;
avis->hdrl.audiotrack.strf.format_tag = 1;
avis->hdrl.audiotrack.strf.channels = 2;
avis->hdrl.audiotrack.strf.samples_per_second = sampling_rate;
avis->hdrl.audiotrack.strf.average_bytes_per_second = sampling_rate * 4;
avis->hdrl.audiotrack.strf.block_align = 4;
avis->hdrl.audiotrack.strf.bits_per_sample = 16;
avis->hdrl.audiotrack.strf.blocksize = 4;
}
}
avi_cscd_dumper::avi_cscd_dumper(const std::string& prefix, const avi_cscd_dumper::global_parameters& global,
const avi_cscd_dumper::segment_parameters& segment) throw(std::bad_alloc, std::runtime_error)
{
dump_prefix = prefix;
if(!global.sampling_rate || global.sampling_rate >= 0xFFFFFFFFUL)
throw std::runtime_error("Sound sampling rate invalid");
if(!segment.fps_n || segment.fps_n >= 0xFFFFFFFFUL)
throw std::runtime_error("FPS numerator invalid");
if(!segment.fps_d || segment.fps_d >= 0xFFFFFFFFUL)
throw std::runtime_error("FPS denominator invalid");
if(segment.dataformat < PIXFMT_RGBX || segment.dataformat > PIXFMT_XBGR)
throw std::runtime_error("Pixel format invalid");
if(!segment.width || segment.width > 0xFFFCU)
throw std::runtime_error("Width invalid");
if(!segment.height || segment.height > 0xFFFCU)
throw std::runtime_error("Height invalid");
if(segment.deflate_level > 9)
throw std::runtime_error("Invalid deflate level");
gp_sampling_rate = global.sampling_rate;
sp_fps_n = segment.fps_n;
sp_fps_d = segment.fps_d;
sp_dataformat = segment.dataformat;
sp_width = segment.width;
sp_height = segment.height;
sp_max_segment_frames = segment.max_segment_frames;
if(segment.default_stride)
sp_stride = 4 * segment.width;
else
sp_stride = segment.stride;
sp_keyframe_distance = segment.keyframe_distance;
sp_deflate_level = segment.deflate_level;
current_major_segment = 0;
next_minor_segment = 0;
current_major_segment_frames = 0;
frames_since_last_keyframe = 0;
avifile_structure = NULL;
buffered_sound_samples = 0;
switch_segments_on_next_frame = false;
frame_period_counter = 0;
quit_requested = false;
flush_requested = false;
flush_requested_forced = false;
frame_processing = false;
frame_pointer = NULL;
exception_error_present = false;
//std::cerr << "A" << std::endl;
dumper_thread_obj dto;
//std::cerr << "B" << std::endl;
frame_thread = new thread_class(dto, this);
//std::cerr << "C" << std::endl;
}
avi_cscd_dumper::~avi_cscd_dumper() throw()
{
try {
end();
} catch(...) {
}
delete frame_thread;
}
avi_cscd_dumper::segment_parameters avi_cscd_dumper::get_segment_parameters() throw()
{
segment_parameters sp;
sp.dataformat = sp_dataformat;
sp.default_stride = false;
sp.deflate_level = sp_deflate_level;
sp.fps_d = sp_fps_d;
sp.fps_n = sp_fps_n;
sp.height = sp_height;
sp.keyframe_distance = sp_keyframe_distance;
sp.stride = sp_stride;
sp.width = sp_width;
return sp;
}
void avi_cscd_dumper::set_segment_parameters(const avi_cscd_dumper::segment_parameters& segment) throw(std::bad_alloc,
std::runtime_error)
{
wait_frame_processing();
if(!segment.fps_n || segment.fps_n >= 0xFFFFFFFFUL)
throw std::runtime_error("FPS numerator invalid");
if(!segment.fps_d || segment.fps_d >= 0xFFFFFFFFUL)
throw std::runtime_error("FPS denominator invalid");
if(segment.dataformat < PIXFMT_RGBX || segment.dataformat > PIXFMT_XBGR)
throw std::runtime_error("Pixel format invalid");
if(!segment.width || segment.width > 0xFFFCU)
throw std::runtime_error("Width invalid");
if(!segment.height || segment.height > 0xFFFCU)
throw std::runtime_error("Height invalid");
if(segment.deflate_level > 9)
throw std::runtime_error("Invalid deflate level");
//Switch all parameters that can't be incompatible.
if(segment.default_stride)
sp_stride = 4 * segment.width;
else
sp_stride = segment.stride;
sp_keyframe_distance = segment.keyframe_distance;
sp_deflate_level = segment.deflate_level;
sp_max_segment_frames = segment.max_segment_frames;
bool incompatible = false;
if(sp_fps_n != segment.fps_n)
incompatible = true;
if(sp_fps_d != segment.fps_d)
incompatible = true;
if(((sp_width + 3) >> 2) != ((segment.width + 3) >> 2))
incompatible = true;
if(((sp_height + 3) >> 2) != ((segment.height + 3) >> 2))
incompatible = true;
if(incompatible) {
spn_dataformat = segment.dataformat;
spn_fps_d = segment.fps_d;
spn_fps_n = segment.fps_n;
spn_height = segment.height;
spn_width = segment.width;
switch_segments_on_next_frame = true;
} else {
sp_dataformat = segment.dataformat;
sp_fps_d = segment.fps_d;
sp_fps_n = segment.fps_n;
sp_height = segment.height;
sp_width = segment.width;
switch_segments_on_next_frame = false;
}
}
void avi_cscd_dumper::audio(const short* audio, size_t samples) throw(std::bad_alloc, std::runtime_error)
{
if(exception_error_present)
throw std::runtime_error(exception_error);
//std::cerr << "Locking lock." << std::endl;
frame_mutex.lock();
//std::cerr << "Locked lock." << std::endl;
for(size_t i = 0; i < samples; i++) {
for(size_t j = 0; j < 2; j++) {
unsigned short as = static_cast<unsigned short>(audio[2 * i + j]) + 32768;
while(buffered_sound_samples * 2 + j >= sound_buffer.size())
sound_buffer.resize(sound_buffer.size() + 128);
sound_buffer[buffered_sound_samples * 2 + j] = as;
}
buffered_sound_samples++;
}
frame_mutex.unlock();
request_flush_buffers(false);
}
void avi_cscd_dumper::audio(const short* laudio, const short* raudio, size_t samples) throw(std::bad_alloc,
std::runtime_error)
{
if(exception_error_present)
throw std::runtime_error(exception_error);
//std::cerr << "Locking lock." << std::endl;
frame_mutex.lock();
//std::cerr << "Locked lock." << std::endl;
for(size_t i = 0; i < samples; i++) {
unsigned short ls = static_cast<unsigned short>(laudio[i]) + 32768;
unsigned short rs = static_cast<unsigned short>(raudio[i]) + 32768;
while(buffered_sound_samples * 2 >= sound_buffer.size())
sound_buffer.resize(sound_buffer.size() + 128);
sound_buffer[buffered_sound_samples * 2 + 0] = ls;
sound_buffer[buffered_sound_samples * 2 + 1] = rs;
buffered_sound_samples++;
}
frame_mutex.unlock();
request_flush_buffers(false);
}
void avi_cscd_dumper::video(const void* framedata) throw(std::bad_alloc, std::runtime_error)
{
if(exception_error_present)
throw std::runtime_error(exception_error);
wait_frame_processing();
//std::cerr << "Locking lock." << std::endl;
frame_mutex.lock();
//std::cerr << "Locked lock." << std::endl;
frame_processing = true;
frame_pointer = framedata;
frame_cond.notify_all();
//std::cerr << "Requesting processing of frame" << std::endl;
frame_mutex.unlock();
}
void avi_cscd_dumper::_video(const void* framedata)
{
buffered_frame frame;
frame.forcebreak = switch_segments_on_next_frame;
//Switch parameters if needed.
if(switch_segments_on_next_frame) {
sp_dataformat = spn_dataformat;
sp_fps_d = spn_fps_d;
sp_fps_n = spn_fps_n;
sp_height = spn_height;
sp_width = spn_width;
switch_segments_on_next_frame = false;
}
frame.compression_level = sp_deflate_level;
frame.fps_d = sp_fps_d;
frame.fps_n = sp_fps_n;
frame.width = sp_width;
frame.height = sp_height;
frame.keyframe = (++frames_since_last_keyframe >= sp_keyframe_distance);
if(frame.keyframe)
frames_since_last_keyframe = 0;
size_t stride = 3 * ((sp_width + 3) >> 2 << 2);
size_t srcstride = 4 * sp_width;
frame.data.resize(stride * ((sp_height + 3) >> 2 << 2));
if(framedata == NULL)
memset(&frame.data[0], 0, frame.data.size());
else {
const unsigned char* _framedata = reinterpret_cast<const unsigned char*>(framedata);
unsigned extheight = (sp_height + 3) >> 2 << 2;
for(unsigned i = 0; i < sp_height; i++)
copy_row(&frame.data[(extheight - i - 1) * stride], _framedata + srcstride * i, sp_width,
sp_dataformat);
for(unsigned i = sp_height; i < extheight; i++)
memset(&frame.data[(extheight - i - 1) * stride], 0, stride);
}
frame_mutex.lock();
frame_processing = false;
frame_pointer = NULL;
frame_cond.notify_all();
frame_mutex.unlock();
frame_buffer.push_back(frame);
flush_buffers(false);
}
void avi_cscd_dumper::end() throw(std::bad_alloc, std::runtime_error)
{
request_flush_buffers(true);
//std::cerr << "Locking lock." << std::endl;
frame_mutex.lock();
//std::cerr << "Locked lock." << std::endl;
quit_requested = true;
frame_cond.notify_all();
//std::cerr << "Requesting quit" << std::endl;
frame_mutex.unlock();
frame_thread->join();
if(avifile_structure)
end_segment();
}
size_t avi_cscd_dumper::emit_frame(const std::vector<unsigned char>& data, bool keyframe, unsigned level)
{
size_t nsize = data.size();
if(previous_frame.size() != nsize) {
previous_frame.resize(nsize);
compression_input.resize(nsize);
//8 bytes for AVI chunk header, 2 bytes for CSCD frame header. 3 bytes for padding.
size_t pmaxsize = compressBound(nsize) + 13;
if(pmaxsize > compression_output.size())
compression_output.resize(pmaxsize);
}
if(!keyframe)
for(size_t i = 0; i < nsize; i++)
compression_input[i] = data[i] - previous_frame[i];
else
memcpy(&compression_input[0], &data[0], data.size());
memcpy(&previous_frame[0], &data[0], nsize);
uLongf l = compression_output.size();
compress2(&compression_output[10], &l, &compression_input[0], compression_input.size(), level);
//Pad the frame.
while((l % 4) != 2)
l++;
compression_output[0] = '0';
compression_output[1] = '0';
compression_output[2] = 'd';
compression_output[3] = 'b'; //strictly speaking, this is wrong, but FCEUX does this when dumping.
write32ule(&compression_output[4], l + 2);
compression_output[8] = (keyframe ? 0x3 : 0x2) | (level << 4);
compression_output[9] = 8;
return l + 10;
}
size_t avi_cscd_dumper::emit_sound(size_t samples)
{
size_t packetsize = 8 + samples * 4;
size_t towrite = samples * 2;
if(packetsize + 3 > compression_output.size())
compression_output.resize(packetsize + 3);
compression_output[0] = '0';
compression_output[1] = '1';
compression_output[2] = 'w';
compression_output[3] = 'b';
write32ule(&compression_output[4], packetsize - 8);
size_t itr = 0;
umutex_class _frame_mutex(frame_mutex);
for(size_t i = 0; i < towrite; i++) {
unsigned short sample = 0;
if(itr < buffered_sound_samples * 2)
sample = sound_buffer[itr++];
write16ule(&compression_output[8 + 2 * i], sample + 32768);
}
if(itr < buffered_sound_samples * 2) {
memmove(&sound_buffer[0], &sound_buffer[itr], sizeof(unsigned short) * (buffered_sound_samples * 2
- itr));
buffered_sound_samples -= itr / 2;
} else
buffered_sound_samples = 0;
while(packetsize & 3)
packetsize++;
return packetsize;
}
void avi_cscd_dumper::start_segment(unsigned major_seg, unsigned minor_seg)
{
struct buffered_frame& f = *frame_buffer.begin();
std::ostringstream name;
name << dump_prefix << "_" << std::setfill('0') << std::setw(4) << major_seg << "_" << std::setfill('0')
<< std::setw(4) << minor_seg << ".avi";
avifile.open(name.str().c_str(), std::ios::out | std::ios::binary);
if(!avifile)
throw std::runtime_error("Can't open AVI file");
avifile_structure = new avi_file_structure;
fill_avi_structure(avifile_structure, (f.width + 3) >> 2 << 2, (f.height + 3) >> 2 << 2, f.fps_n,
f.fps_d, gp_sampling_rate);
avifile_structure->start_data(avifile);
frame_period_counter = 0;
}
void avi_cscd_dumper::end_segment()
{
if(!avifile_structure)
return;
avifile_structure->finish_avi(avifile);
avifile.flush();
if(!avifile)
throw std::runtime_error("Can't finish AVI");
avifile.close();
delete avifile_structure;
avifile_structure = NULL;
}
bool avi_cscd_dumper::restart_segment_if_needed(bool force_break)
{
if(!avifile_structure) {
start_segment(current_major_segment, next_minor_segment++);
return true;
}
if(sp_max_segment_frames && current_major_segment_frames >= sp_max_segment_frames) {
end_segment();
current_major_segment++;
next_minor_segment = 0;
start_segment(current_major_segment, next_minor_segment++);
current_major_segment_frames = 0;
return true;
}
if(force_break) {
end_segment();
start_segment(current_major_segment, next_minor_segment++);
return true;
}
if(avifile_structure->size() > AVI_CUTOFF_SIZE) {
end_segment();
start_segment(current_major_segment, next_minor_segment++);
return true;
}
return false;
}
void avi_cscd_dumper::write_frame_av(size_t samples)
{
struct buffered_frame& f = *frame_buffer.begin();
std::vector<unsigned char>& data = f.data;
bool keyframe = f.keyframe;
unsigned level = f.compression_level;
bool force_break = f.forcebreak;
size_t size;
bool tmp = restart_segment_if_needed(force_break);
keyframe = keyframe || tmp;
size = emit_frame(data, keyframe, level);
emit_frame_stream(size, keyframe);
size = emit_sound(samples);
emit_sound_stream(size, samples);
current_major_segment_frames++;
frame_buffer.erase(frame_buffer.begin());
}
void avi_cscd_dumper::emit_frame_stream(size_t size, bool keyframe)
{
avifile_structure->idx1.add_entry(index_entry(0x62643030UL, keyframe ? 0x10 : 0,
avifile_structure->movi.payload_size + 4, size - 8));
avifile.write(reinterpret_cast<const char*>(&compression_output[0]), size);
avifile_structure->hdrl.videotrack.strh.add_frames(1);
avifile_structure->movi.add_payload(size);
}
void avi_cscd_dumper::emit_sound_stream(size_t size, size_t samples)
{
avifile_structure->idx1.add_entry(index_entry(0x62773130UL, 0x10,
avifile_structure->movi.payload_size + 4, size - 8));
avifile.write(reinterpret_cast<const char*>(&compression_output[0]), size);
avifile_structure->hdrl.audiotrack.strh.add_frames(samples);
avifile_structure->movi.add_payload(size);
}
size_t avi_cscd_dumper::samples_for_next_frame()
{
//The average number of samples per frame needs to be:
//samplerate * fps_d / fps_n.
struct buffered_frame& f = *frame_buffer.begin();
unsigned long critical = static_cast<uint64_t>(gp_sampling_rate) * f.fps_d % f.fps_n;
unsigned long ret = static_cast<uint64_t>(gp_sampling_rate) * f.fps_d / f.fps_n;
if(static_cast<uint64_t>(frame_period_counter) * critical % f.fps_n < critical)
ret++;
return ret;
}
void avi_cscd_dumper::flush_buffers(bool forced)
{
while(!frame_buffer.empty()) {
unsigned long s_fps_n = frame_buffer.begin()->fps_n;
size_t samples = samples_for_next_frame();
frame_mutex.lock();
size_t asamples = buffered_sound_samples;
frame_mutex.unlock();
if(!forced && asamples < samples)
break;
write_frame_av(samples);
frame_period_counter++;
frame_period_counter %= s_fps_n;
}
}
void avi_cscd_dumper::request_flush_buffers(bool forced)
{
//std::cerr << "Locking lock." << std::endl;
frame_mutex.lock();
//std::cerr << "Locked lock." << std::endl;
flush_requested = true;
flush_requested_forced = forced;
frame_cond.notify_all();
//std::cerr << "Requesting buffer flush (" << flush_requested_forced << ")" << std::endl;
frame_mutex.unlock();
}
bool avi_cscd_dumper::is_frame_processing() throw()
{
return frame_processing;
}
void avi_cscd_dumper::wait_frame_processing() throw()
{
umutex_class _frame_mutex(frame_mutex);
while(frame_processing) {
//std::cerr << "Waiting for frame to process." << std::endl;
frame_cond.wait(_frame_mutex);
}
//std::cerr << "Ok, frame processed, returning" << std::endl;
}
int avi_cscd_dumper::encode_thread()
{
try {
//std::cerr << "Encoder thread ready." << std::endl;
start:
frame_mutex.lock();
if(quit_requested && !frame_pointer && !flush_requested) {
//std::cerr << "OK, quitting on request." << std::endl;
goto end;
}
if(frame_pointer || frame_processing) {
//std::cerr << "Servicing video frame" << std::endl;
frame_mutex.unlock();
const void* f = (const void*)frame_pointer;
_video(f);
frame_mutex.lock();
}
if(flush_requested) {
//std::cerr << "Servicing flush" << std::endl;
frame_mutex.unlock();
flush_buffers(flush_requested_forced);
frame_mutex.lock();
flush_requested = false;
}
frame_mutex.unlock();
{
umutex_class _frame_mutex(frame_mutex);
while(!quit_requested && !frame_pointer && !flush_requested && !frame_processing) {
//std::cerr << "Waiting for work." << std::endl;
frame_cond.wait(_frame_mutex);
}
}
goto start;
end:
frame_mutex.unlock();
return 0;
} catch(std::exception& e) {
set_capture_error(e.what());
return 1;
}
}
void avi_cscd_dumper::set_capture_error(const std::string& err)
{
frame_mutex.lock();
exception_error = err;
frame_mutex.unlock();
exception_error_present = true;
}

View file

@ -1,4 +1,5 @@
#include "video/sox.hpp"
#include "library/serialization.hpp"
#include <iostream>
@ -23,14 +24,7 @@ namespace
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;
write64ule(buf, v2);
}
}
@ -42,16 +36,9 @@ sox_dumper::sox_dumper(const std::string& filename, double samplerate, uint32_t
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.
write64ule(buffer, 0x1C586F532E); //Magic and header size.
write_double(buffer + 16, samplerate);
buffer[24] = channels;
buffer[25] = channels >> 8;
buffer[26] = channels >> 16;
buffer[27] = channels >> 24;
write32ule(buffer + 24, channels);
sox_file.write(reinterpret_cast<char*>(buffer), 32);
if(!sox_file)
throw std::runtime_error("Can't write audio header");
@ -77,14 +64,7 @@ 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;
write64ule(buffer, raw_samples);
sox_file.write(reinterpret_cast<char*>(buffer), 8);
if(!sox_file)
throw std::runtime_error("Can't fixup audio header");
@ -93,13 +73,8 @@ void sox_dumper::close() throw(std::bad_alloc, std::runtime_error)
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;
}
for(size_t i = 0; i < samplebuffer.size(); ++i)
write32ule(&databuf[4 * i], static_cast<uint32_t>(samplebuffer[i]));
sox_file.write(&databuf[0], databuf.size());
if(!sox_file)
throw std::runtime_error("Failed to dump sample");