// license:BSD-3-Clause // copyright-holders:Aaron Giles /*************************************************************************** recording.cpp MAME AVI/MNG video recording routines. ***************************************************************************/ #include "emu.h" #include "fileio.h" #include "main.h" #include "screen.h" #include "aviio.h" #include "png.h" namespace { class avi_movie_recording : public movie_recording { public: avi_movie_recording(screen_device *screen) : movie_recording(screen) { } bool initialize(running_machine &machine, std::unique_ptr &&file, int32_t width, int32_t height); virtual bool add_sound_to_recording(const s16 *sound, int numsamples) override; protected: virtual bool append_single_video_frame(bitmap_rgb32 &bitmap, const rgb_t *palette, int palette_entries) override; private: avi_file::ptr m_avi_file; // handle to the open movie file }; class mng_movie_recording : public movie_recording { public: mng_movie_recording(screen_device *screen, std::map &&info_fields); ~mng_movie_recording(); bool initialize(std::unique_ptr &&file, bitmap_t &snap_bitmap); virtual bool add_sound_to_recording(const s16 *sound, int numsamples) override; protected: virtual bool append_single_video_frame(bitmap_rgb32 &bitmap, const rgb_t *palette, int palette_entries) override; private: std::unique_ptr m_mng_file; // handle to the open movie file std::map m_info_fields; }; }; //************************************************************************** // MOVIE RECORDING //************************************************************************** //------------------------------------------------- // movie_recording - constructor //------------------------------------------------- movie_recording::movie_recording(screen_device *screen) : m_screen(screen) , m_frame_period(attotime::zero) , m_next_frame_time(attotime::zero) , m_frame(0) { } //------------------------------------------------- // movie_recording - destructor //------------------------------------------------- movie_recording::~movie_recording() { } //------------------------------------------------- // movie_recording::append_video_frame //------------------------------------------------- bool movie_recording::append_video_frame(bitmap_rgb32 &bitmap, attotime curtime) { // identify the palette bool has_palette = screen() && screen()->has_palette(); const rgb_t *palette = has_palette ? screen()->palette().palette()->entry_list_adjusted() : nullptr; int palette_entries = has_palette ? screen()->palette().entries() : 0; // keep appending frames until we're at curtime while (next_frame_time() <= curtime) { // append this bitmap as a single frame if (!append_single_video_frame(bitmap, palette, palette_entries)) return false; m_frame++; // advance time set_next_frame_time(next_frame_time() + frame_period()); } return true; } //------------------------------------------------- // movie_recording::create - creates a new recording // for the specified format //------------------------------------------------- movie_recording::ptr movie_recording::create(running_machine &machine, screen_device *screen, format fmt, std::unique_ptr &&file, bitmap_rgb32 &snap_bitmap) { movie_recording::ptr result; switch (fmt) { case movie_recording::format::AVI: { auto avi_recording = std::make_unique(screen); if (avi_recording->initialize(machine, std::move(file), snap_bitmap.width(), snap_bitmap.height())) result = std::move(avi_recording); } break; case movie_recording::format::MNG: { std::map info_fields; info_fields["Software"] = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version()); info_fields["System"] = std::string(machine.system().manufacturer).append(" ").append(machine.system().type.fullname()); auto mng_recording = std::make_unique(screen, std::move(info_fields)); if (mng_recording->initialize(std::move(file), snap_bitmap)) result = std::move(mng_recording); } break; default: throw false; } // if we successfully create a recording, set the current time and return it if (result) result->set_next_frame_time(machine.time()); return result; } //------------------------------------------------- // movie_recording::format_file_extension //------------------------------------------------- const char *movie_recording::format_file_extension(movie_recording::format fmt) { switch (fmt) { case format::AVI: return "avi"; case format::MNG: return "mng"; default: throw false; } } //------------------------------------------------- // avi_movie_recording::initialize //------------------------------------------------- bool avi_movie_recording::initialize(running_machine &machine, std::unique_ptr &&file, int32_t width, int32_t height) { // we only use the file we're passed to get the full path std::string fullpath = file->fullpath(); file.reset(); // build up information about this new movie avi_file::movie_info info; info.video_format = 0; info.video_timescale = 0x3fff'fffc; // multiple of 60, for screenless machines info.video_sampletime = screen() ? screen()->frame_period().as_ticks(info.video_timescale) : 0x0111'1111; info.video_numsamples = 0; info.video_width = width; info.video_height = height; info.video_depth = 24; info.audio_format = 0; info.audio_timescale = machine.sample_rate(); info.audio_sampletime = 1; info.audio_numsamples = 0; info.audio_channels = 2; info.audio_samplebits = 16; info.audio_samplerate = machine.sample_rate(); // compute the frame time set_frame_period(attotime::from_ticks(info.video_sampletime, info.video_timescale)); // create the file avi_file::error avierr = avi_file::create(fullpath, info, m_avi_file); if (avierr != avi_file::error::NONE) osd_printf_error("Error creating AVI: %s\n", avi_file::error_string(avierr)); return avierr == avi_file::error::NONE; } //------------------------------------------------- // avi_movie_recording::append_single_video_frame //------------------------------------------------- bool avi_movie_recording::append_single_video_frame(bitmap_rgb32 &bitmap, const rgb_t *palette, int palette_entries) { avi_file::error avierr = m_avi_file->append_video_frame(bitmap); return avierr == avi_file::error::NONE; } //------------------------------------------------- // avi_movie_recording::append_video_frame //------------------------------------------------- bool avi_movie_recording::add_sound_to_recording(const s16 *sound, int numsamples) { auto profile = g_profiler.start(PROFILER_MOVIE_REC); // write the next frame avi_file::error avierr = m_avi_file->append_sound_samples(0, sound + 0, numsamples, 1); if (avierr == avi_file::error::NONE) avierr = m_avi_file->append_sound_samples(1, sound + 1, numsamples, 1); return avierr == avi_file::error::NONE; } //------------------------------------------------- // mng_movie_recording - constructor //------------------------------------------------- mng_movie_recording::mng_movie_recording(screen_device *screen, std::map &&info_fields) : movie_recording(screen) , m_info_fields(std::move(info_fields)) { } //------------------------------------------------- // mng_movie_recording - destructor //------------------------------------------------- mng_movie_recording::~mng_movie_recording() { if (m_mng_file) util::mng_capture_stop(*m_mng_file); } //------------------------------------------------- // mng_movie_recording::initialize //------------------------------------------------- bool mng_movie_recording::initialize(std::unique_ptr &&file, bitmap_t &snap_bitmap) { // compute the frame time (MNG rate is an unsigned integer) attotime period = screen() ? screen()->frame_period() : attotime::from_hz(screen_device::DEFAULT_FRAME_RATE); u32 rate = u32(period.as_hz()); set_frame_period(attotime::from_hz(rate)); m_mng_file = std::move(file); std::error_condition const pngerr = util::mng_capture_start(*m_mng_file, snap_bitmap, rate); if (pngerr) osd_printf_error("Error capturing MNG (%s:%d %s)\n", pngerr.category().name(), pngerr.value(), pngerr.message()); return !pngerr; } //------------------------------------------------- // mng_movie_recording::append_single_video_frame //------------------------------------------------- bool mng_movie_recording::append_single_video_frame(bitmap_rgb32 &bitmap, const rgb_t *palette, int palette_entries) { // set up the text fields in the movie info util::png_info pnginfo; if (current_frame() == 0) { for (auto &ent : m_info_fields) pnginfo.add_text(ent.first, ent.second); } std::error_condition const error = util::mng_capture_frame(*m_mng_file, pnginfo, bitmap, palette_entries, palette); return !error; } //------------------------------------------------- // mng_movie_recording::add_sound_to_recording //------------------------------------------------- bool mng_movie_recording::add_sound_to_recording(const s16 *sound, int numsamples) { // not supported; do nothing return true; }