Refactor AVI dumping a lot (WIP)
This commit is contained in:
parent
fab8d8f4e3
commit
043cac8894
26 changed files with 2030 additions and 1211 deletions
18
Makefile
18
Makefile
|
@ -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
|
||||
|
|
112
include/library/workthread.hpp
Normal file
112
include/library/workthread.hpp
Normal 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
275
include/video/avi/codec.hpp
Normal 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
|
63
include/video/avi/samplequeue.hpp
Normal file
63
include/video/avi/samplequeue.hpp
Normal 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
|
|
@ -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
|
33
include/video/avi/timer.hpp
Normal file
33
include/video/avi/timer.hpp
Normal 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
|
60
include/video/avi/writer.hpp
Normal file
60
include/video/avi/writer.hpp
Normal 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
|
|
@ -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
118
src/library/workthread.cpp
Normal 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);
|
||||
}
|
|
@ -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
354
src/video/avi.cpp
Normal 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
22
src/video/avi/Makefile
Normal 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
319
src/video/avi/codec.cpp
Normal 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>;
|
27
src/video/avi/codec/Makefile
Normal file
27
src/video/avi/codec/Makefile
Normal 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
|
17
src/video/avi/codec/audio/Makefile
Normal file
17
src/video/avi/codec/audio/Makefile
Normal 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
|
57
src/video/avi/codec/audio/pcm.cpp
Normal file
57
src/video/avi/codec/audio/pcm.cpp
Normal 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;});
|
||||
}
|
17
src/video/avi/codec/video/Makefile
Normal file
17
src/video/avi/codec/video/Makefile
Normal 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
|
180
src/video/avi/codec/video/cscd.cpp
Normal file
180
src/video/avi/codec/video/cscd.cpp
Normal 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);});
|
||||
}
|
93
src/video/avi/codec/video/uncompressed.cpp
Normal file
93
src/video/avi/codec/video/uncompressed.cpp
Normal 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;});
|
||||
}
|
68
src/video/avi/samplequeue.cpp
Normal file
68
src/video/avi/samplequeue.cpp
Normal 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);
|
||||
}
|
|
@ -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
49
src/video/avi/timer.cpp
Normal 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
89
src/video/avi/writer.cpp
Normal 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;
|
||||
}
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
|
|
Loading…
Add table
Reference in a new issue