X-Git-Url: http://git.cinelerra-gg.org/git/?a=blobdiff_plain;f=cinelerra-5.1%2Fcinelerra%2Frecord.C;fp=cinelerra-5.1%2Fcinelerra%2Frecord.C;h=9a24b98129f0ee654160581366394f2473b7a85f;hb=30bdb85eb33a8ee7ba675038a86c6be59c43d7bd;hp=0000000000000000000000000000000000000000;hpb=52fcc46226f9df46f9ce9d0566dc568455a7db0b;p=goodguy%2Fhistory.git diff --git a/cinelerra-5.1/cinelerra/record.C b/cinelerra-5.1/cinelerra/record.C new file mode 100644 index 00000000..9a24b981 --- /dev/null +++ b/cinelerra-5.1/cinelerra/record.C @@ -0,0 +1,2039 @@ +/* + * CINELERRA + * Copyright (C) 2009-2013 Adam Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "asset.h" +#include "assets.h" +#include "audiodevice.h" +#include "awindow.h" +#include "awindowgui.h" +#include "batch.h" +#include "bchash.h" +#include "channel.h" +#include "channeldb.h" +#include "channelpicker.h" +#include "clip.h" +#include "commercials.h" +#include "condition.h" +#include "cwindow.h" +#include "devicedvbinput.h" +#include "drivesync.h" +#include "edl.h" +#include "edlsession.h" +#include "errorbox.h" +#include "file.h" +#include "filexml.h" +#include "filesystem.h" +#include "filethread.h" +#include "formatcheck.h" +#include "indexfile.h" +#include "keys.h" +#include "language.h" +#include "localsession.h" +#include "libdv.h" +#include "libmjpeg.h" +#include "mainmenu.h" +#include "mainundo.h" +#include "mwindow.h" +#include "mwindowgui.h" +#include "mutex.h" +#include "picture.h" +#include "playbackengine.h" +#include "preferences.h" +#include "record.h" +#include "recordaudio.h" +#include "recordconfig.h" +#include "recordgui.h" +#include "recordlabel.h" +#include "recordmonitor.h" +#include "recordprefs.inc" +#include "recordthread.h" +#include "recordvideo.h" +#include "removefile.h" +#include "mainsession.h" +#include "sighandler.h" +#include "testobject.h" +#include "theme.h" +#include "timebar.h" +#include "tracks.h" +#include "videoconfig.h" +#include "videodevice.h" + +#include +#include +#include + + +RecordMenuItem::RecordMenuItem(MWindow *mwindow) + : BC_MenuItem(_("Record..."), "r", 'r') +{ + this->mwindow = mwindow; + record = new Record(mwindow, this); +} + +RecordMenuItem::~RecordMenuItem() +{ + delete record; +} + +int RecordMenuItem::handle_event() +{ + record->start(); + return 1; +} + + +Record::Record(MWindow *mwindow, RecordMenuItem *menu_item) + : Thread(1, 0, 0), + record_batches(mwindow) +{ + this->mwindow = mwindow; + this->menu_item = menu_item; + mwindow->gui->record = this; + adevice = 0; + vdevice = 0; + file = 0; + picture = new PictureConfig(); + channeldb = new ChannelDB; + master_channel = new Channel; + record_channel = new RecordChannel(this); + channel = 0; + current_channel = 0; + default_asset = 0; + load_mode = 0 ; + pause_lock = new Mutex("Record::pause_lock"); + init_lock = new Condition(0,"Record::init_lock"); + record_audio = 0; + record_video = 0; + record_thread = 0; + record_monitor = 0; + record_gui = 0; + session_sample_offset = 0; + device_sample_offset = 0; + recording = 0; + capturing = 0; + single_frame = 0; + writing_file = 0; + drop_overrun_frames = 0; + fill_underrun_frames = 0; + power_off = 0; + commercial_check = skimming_active = 0; + commercial_start_time = -1; + commercial_fd = -1; + deletions = 0; + status_color = -1; + keybfr[0] = 0; + last_key = -1; + do_audio = 0; + do_video = 0; + current_frame = written_frames = total_frames = 0; + current_sample = written_samples = total_samples = 0; + audio_time = video_time = -1.; + play_gain = mute_gain = 1.; + drivesync = 0; + input_threads_pausing = 0; + window_lock = new Mutex("Record::window_lock"); + timer_lock = new Mutex("Record::timer_lock"); + file_lock = new Mutex("Record::file_lock"); + adevice_lock = new Mutex("Record::adevice_lock"); + vdevice_lock = new Mutex("Record::vdevice_lock"); + batch_lock = new Mutex("Record::batch_lock"); + skim_thread = new SkimDbThread(); + cutads_status = new RecordCutAdsStatus(this); + blink_status = new RecordBlinkStatus(this); +} + +Record::~Record() +{ + mwindow->gui->record = 0; + stop(); join(); + delete blink_status; + delete cutads_status; + stop_skimming(); + delete skim_thread; + delete deletions; + delete picture; + delete channeldb; + delete record_channel; + delete master_channel; + delete window_lock; + delete timer_lock; + delete record_audio; + delete record_video; + delete adevice_lock; + delete vdevice_lock; + delete batch_lock; + delete file_lock; + delete pause_lock; + delete init_lock; +} + +int Record::load_defaults() +{ + + char string[BCTEXTLEN]; + BC_Hash *defaults = mwindow->defaults; + EDLSession *session = SESSION; + default_asset->copy_from(session->recording_format, 0); + default_asset->channels = session->aconfig_in->channels; + default_asset->sample_rate = session->aconfig_in->in_samplerate; + default_asset->frame_rate = session->vconfig_in->in_framerate; + default_asset->width = session->vconfig_in->w; + default_asset->height = session->vconfig_in->h; + default_asset->layers = 1; +// Fix encoding parameters depending on driver. +// These are locked by a specific driver. + const char *vcodec = 0; + switch( session->vconfig_in->driver ) { + case CAPTURE_LML: + case CAPTURE_BUZ: + vcodec = CODEC_TAG_MJPEG; + break; + case CAPTURE_DVB: + case VIDEO4LINUX2MPEG: + break; + case VIDEO4LINUX2JPEG: + vcodec = CODEC_TAG_MJPEG; + break; + case CAPTURE_FIREWIRE: + case CAPTURE_IEC61883: + vcodec = CODEC_TAG_DVSD; + break; + } + if( vcodec ) + strcpy(default_asset->vcodec, vcodec); + + record_batches.load_defaults(channeldb, this); + + int cur_chan_no = defaults->get("RECORD_CURRENT_CHANNEL", 0); + current_channel = channeldb->get(cur_chan_no); + load_mode = defaults->get("RECORD_LOADMODE", LOADMODE_PASTE); + monitor_audio = defaults->get("RECORD_MONITOR_AUDIO", 1); + metering_audio = defaults->get("RECORD_METERING_AUDIO", 1); + monitor_video = defaults->get("RECORD_MONITOR_VIDEO", 1); + video_window_open = defaults->get("RECORD_MONITOR_OPEN", 1); + video_x = defaults->get("RECORD_VIDEO_X", 0); + video_y = defaults->get("RECORD_VIDEO_Y", 0); + video_zoom = defaults->get("RECORD_VIDEO_Z", (float)1); + picture->load_defaults(); + reverse_interlace = defaults->get("REVERSE_INTERLACE", 0); + for( int i=0; iget(string, 0); + } + drop_overrun_frames = defaults->get("DROP_OVERRUN_FRAMES", 0); + fill_underrun_frames = defaults->get("FILL_UNDERRUN_FRAMES", 0); + commercial_check = defaults->get("COMMERCIAL_CHECK", 0); + return 0; +} + +int Record::save_defaults() +{ + char string[BCTEXTLEN]; + BC_Hash *defaults = mwindow->defaults; + set_editing_batch(0); +// Save default asset path but not the format because that's +// overridden by the driver. +// The format is saved in preferences. + if( record_batches.total() ) + strcpy(default_asset->path, record_batches[0]->asset->path); + default_asset->save_defaults(defaults, "RECORD_", 0, 0, 0, 0, 0); + + record_batches.save_defaults(channeldb); + + int cur_chan_no = channeldb->number_of(current_channel); + defaults->update("RECORD_CURRENT_CHANNEL", cur_chan_no); + defaults->update("RECORD_LOADMODE", load_mode); + defaults->update("RECORD_MONITOR_AUDIO", monitor_audio); + defaults->update("RECORD_METERING_AUDIO", metering_audio); + defaults->update("RECORD_MONITOR_VIDEO", monitor_video); + defaults->update("RECORD_MONITOR_OPEN", video_window_open); + defaults->update("RECORD_VIDEO_X", video_x); + defaults->update("RECORD_VIDEO_Y", video_y); + defaults->update("RECORD_VIDEO_Z", video_zoom); + picture->save_defaults(); + defaults->update("REVERSE_INTERLACE", reverse_interlace); + for( int i=0; iupdate(string, dc_offset[i]); + } + defaults->update("DROP_OVERRUN_FRAMES", drop_overrun_frames); + defaults->update("FILL_UNDERRUN_FRAMES", fill_underrun_frames); + defaults->update("COMMERCIAL_CHECK", commercial_check); +SET_TRACE + + return 0; +} + +void Record::configure_batches() +{ +// printf("Record::configure_batches %d\n",__LINE__); +// default_assset->dump(); + strcpy(record_batches[0]->asset->path, default_asset->path); + int total_batches = record_batches.total(); + for( int i=0; iasset->copy_format(default_asset); + } +} + +void Record::run() +{ + int result = 0; + record_gui = 0; +// Default asset forms the first path in the batch capture +// and the file format for all operations. + default_asset = new Asset; + +// Determine information about the device. + AudioInConfig *aconfig_in = SESSION->aconfig_in; + VideoInConfig *vconfig_in = SESSION->vconfig_in; + int driver = vconfig_in->driver; + VideoDevice::load_channeldb(channeldb, vconfig_in); + fixed_compression = VideoDevice::is_compressed(driver, 0, 1); + load_defaults(); +// Apply a major kludge + if( fixed_compression ) { + VideoDevice device; + device.fix_asset(default_asset, driver); + } + default_asset->channels = aconfig_in->channels; + VideoDevice::save_channeldb(channeldb, vconfig_in); + save_defaults(); + mwindow->save_defaults(); + configure_batches(); + set_current_batch(0); + set_editing_batch(0); + +// Run recordgui + edl = new EDL; + edl->create_objects(); + edl->session->output_w = default_asset->width; + edl->session->output_h = default_asset->height; + edl->session->aspect_w = SESSION->aspect_w; + edl->session->aspect_h = SESSION->aspect_h; + + window_lock->lock("Record::run 3"); + record_gui = new RecordGUI(mwindow, this); + record_gui->create_objects(); + + record_monitor = new RecordMonitor(mwindow, this); + record_monitor->create_objects(); + record_gui->update_batch_sources(); + + record_gui->show_window(); + record_gui->flush(); + + if( mwindow->gui->remote_control->deactivate() ) + mwindow->gui->record_remote_handler->activate(); + + if( video_window_open ) { + record_monitor->window->show_window(); + record_monitor->window->raise_window(); + record_monitor->window->flush(); + } + + window_lock->unlock(); + channel = 0; + start_input_threads(); + update_position(); + init_lock->unlock(); + + result = record_gui->run_window(); +// record gui operates here until window is closed +// wait for it + if( record_gui->get_window_lock() ) { + record_gui->lock_window(); + record_gui->unlock_window(); + } + +// Need to stop everything this time + + stop_commercial_capture(0); + int video_stream = -1; + if( default_asset->format == FILE_MPEG ) { + Channel *channel = get_current_channel(); + if( channel ) + video_stream = channel->video_stream; + } + stop(); + edl->Garbage::remove_user(); + + if( mwindow->gui->remote_control->deactivate() ) + mwindow->gui->cwindow_remote_handler->activate(); + +// Save everything again + save_defaults(); + +// Paste into EDL + if( !result && load_mode != LOADMODE_NOTHING ) { + mwindow->gui->lock_window("Record::run"); + ArrayList new_edls; +// Paste assets + int total_batches = record_batches.total(); + for( int i=0; iasset; + if( batch->recorded ) { + EDL *new_edl = new EDL; + mwindow->remove_asset_from_caches(asset); + new_edl->create_objects(); + new_edl->copy_session(mwindow->edl); + mwindow->asset_to_edl(new_edl, asset, batch->labels); + new_edls.append(new_edl); + } + } + + if(new_edls.total) { + mwindow->undo->update_undo_before(); +// For pasting, clear the active region + if(load_mode == LOADMODE_PASTE) + mwindow->clear(0); + + mwindow->paste_edls(&new_edls, load_mode, 0, -1, + SESSION->labels_follow_edits, + SESSION->plugins_follow_edits, + SESSION->autos_follow_edits, + 0);// overwrite + for( int i=0; iGarbage::remove_user(); + new_edls.remove_all(); + if( video_stream >= 0 ) { + mwindow->select_asset(video_stream, -1); + mwindow->fit_selection(); + } + mwindow->save_backup(); + mwindow->undo->update_undo_after(_("record"), LOAD_ALL); + mwindow->restart_brender(); + mwindow->update_plugin_guis(); + mwindow->gui->update(1, 2, 1, 1, 1, 1, 0); + mwindow->sync_parameters(CHANGE_ALL); + } + mwindow->gui->unlock_window(); + + AWindowGUI *agui = mwindow->awindow->gui; + agui->async_update_assets(); + } + +// Delete everything + record_batches.clear(); + default_asset->Garbage::remove_user(); +} + +void Record::stop() +{ + stop_operation(); + window_lock->lock("Record::stop"); + delete record_thread; record_thread = 0; + delete record_monitor; record_monitor = 0; + delete record_gui; record_gui = 0; + window_lock->unlock(); +} + +void Record::activate_batch(int number) +{ + if( number != current_batch() ) { + stop_writing(); + set_current_batch(number); + record_gui->update_batches(); + record_gui->update_position(current_display_position()); + record_gui->update_batch_tools(); + } +} + + +void Record::delete_index_file(Asset *asset) +{ + IndexFile::delete_index(mwindow->preferences, asset, ".toc"); + IndexFile::delete_index(mwindow->preferences, asset, ".idx"); + IndexFile::delete_index(mwindow->preferences, asset, ".mkr"); +} + +void Record::delete_batch() +{ +// Abort if one batch left + int edit_batch = editing_batch(); + int total_batches = record_batches.total(); + if( total_batches > 1 && edit_batch < total_batches ) { + int cur_batch = current_batch(); + Batch *batch = record_batches[edit_batch]; + record_batches.remove(batch); delete batch; + if( cur_batch == edit_batch ) stop_writing(); + record_gui->update_batches(); + record_gui->update_batch_tools(); + } +} + +void Record::change_editing_batch(int number) +{ + set_editing_batch(number); + record_gui->update_batch_tools(); +} + +Batch* Record::new_batch() +{ + Batch *batch = new Batch(mwindow, this); +//printf("Record::new_batch 1\n"); + batch->create_objects(); + record_batches.append(batch); + batch->asset->copy_format(default_asset); + +//printf("Record::new_batch 1\n"); + batch->create_default_path(); + Batch *edit_batch = get_editing_batch(); + if( edit_batch ) + batch->copy_from(edit_batch); + int total_batches = record_batches.total(); + set_editing_batch(total_batches - 1); +//printf("Record::new_batch 1\n"); +// Update GUI if created yet + if(record_gui) + record_gui->update_batch_tools(); +//printf("Record::new_batch 2\n"); + return batch; +} + +int Record::current_batch() +{ + return record_batches.current_item; +} + +int Record::set_current_batch(int i) +{ + return record_batches.current_item = i; +} + +int Record::editing_batch() +{ + return record_batches.editing_item; +} + +int Record::set_editing_batch(int i) +{ + return record_batches.editing_item = i; +} + +int Record::delete_output_file() +{ + Batch *batch = get_current_batch(); +// Delete old file + if( !file && batch && !access(batch->asset->path, F_OK) ) { + batch->set_notice(_("Deleting")); + record_gui->update_batches(); + Asset *asset = batch->asset; + remove_file(asset->path); + mwindow->remove_asset_from_caches(asset); + delete_index_file(asset); + batch->clear_notice(); + record_gui->update_batches(); + } while(0); + return 0; +} + +int Record::open_output_file() +{ + int result = 0; + file_lock->lock(); + Batch *batch = get_current_batch(); + if( !file && batch ) { + delete_output_file(); + Asset *asset = batch->asset; + asset->frame_rate = default_asset->frame_rate; + asset->sample_rate = default_asset->sample_rate; + asset->width = default_asset->width; + asset->height = default_asset->height; + int wr = 1; + if( default_asset->format == FILE_MPEG ) { + asset->layers = vdevice ? vdevice->total_video_streams() : 0; + asset->channels = adevice ? adevice->total_audio_channels() : 0; + wr = 0; + } + file = new File; + result = file->open_file(mwindow->preferences, asset, 0, wr); + if( !result ) { + mwindow->sighandler->push_file(file); + batch->recorded = 1; + file->set_processors(mwindow->preferences->real_processors); + record_gui->update_batches(); + } + else { + delete file; + file = 0; + } + } + file_lock->unlock(); + return result; +} + +void Record::start() +{ + if( running() ) { + window_lock->lock("Record::start"); + record_gui->lock_window("Record::start"); + record_gui->raise_window(); + record_gui->unlock_window(); + window_lock->unlock(); + } + else { + stop(); join(); + init_lock->reset(); + Thread::start(); + } +} + +void Record::start_over() +{ + stop_writing(); + Batch *batch = get_current_batch(); + if( batch ) batch->start_over(); + record_gui->update_batches(); +} + +void Record::stop_operation() +{ + stop_writing(); + stop_input_threads(); +} + +int Record::cron_active() +{ + return !record_thread ? 0 : record_thread->cron_active; +} + +void Record::close_output_file() +{ + file_lock->lock(); + if( file ) { + mwindow->sighandler->pull_file(file); + file->close_file(); + delete file; + file = 0; + } + file_lock->unlock(); + record_gui->update_batches(); +} + +void Record::toggle_label() +{ + Batch *batch = get_current_batch(); + if( batch ) batch->toggle_label(current_display_position()); + record_gui->update_labels(current_display_position()); +} + +void Record::clear_labels() +{ + Batch *batch = get_current_batch(); + if( batch ) batch->clear_labels(); + record_gui->update_labels(0); +} + +int Record::get_fragment_samples() +{ + const int min_samples = 1024, max_samples = 32768; + int samples, low_bit; + samples = default_asset->sample_rate / SESSION->record_speed; + if( samples < min_samples ) samples = min_samples; + else if( samples > max_samples ) samples = max_samples; + // loop until only one bit left standing + while( (low_bit=(samples & ~(samples-1))) != samples ) + samples += low_bit; + return samples; +} + +int Record::get_buffer_samples() +{ + int fragment_samples = get_fragment_samples(); + if( !fragment_samples ) return 0; + int fragments = (SESSION->record_write_length+fragment_samples-1) / fragment_samples; + if( fragments < 1 ) fragments = 1; + return fragments * fragment_samples; +} + +Batch* Record::get_current_batch() +{ + return record_batches.get_current_batch(); +} + +Batch* Record::get_editing_batch() +{ + return record_batches.get_editing_batch(); +} + +int Record::get_next_batch(int incr) +{ + int i = current_batch(); + if( i >= 0 ) { + i += incr; + int total_batches = record_batches.total(); + while( i < total_batches ) { + if( record_batches[i]->enabled ) return i; + ++i; + } + } + return -1; +} + +const char* Record::current_mode() +{ + Batch *batch = get_current_batch(); + return batch ? Batch::mode_to_text(batch->record_mode) : ""; +} + +double Record::current_display_position() +{ +//printf("Record::current_display_position " +// _LD " " _LD "\n", total_samples, total_frames) + double result = -1.; + Asset *asset = default_asset; + if( writing_file ) { + if( asset->video_data && record_video ) + result = (double) written_frames / asset->frame_rate; + else if( asset->audio_data && record_audio ) + result = (double) written_samples / asset->sample_rate; + } + if( result < 0. ) { + if( asset->video_data && record_video ) + result = (double) total_frames / asset->frame_rate; + else if( asset->audio_data && record_audio ) + result = (double) total_samples / asset->sample_rate; + else + result = (double) total_time.get_difference() / 1000.; + } + return result; +} + +const char* Record::current_source() +{ + Batch *batch = get_current_batch(); + return batch ? batch->get_source_text() : _("Unknown"); +} + +Asset* Record::current_asset() +{ + Batch *batch = get_current_batch(); + return batch ? batch->asset : 0; +} + +Channel *Record::get_current_channel() +{ + return current_channel; +} + +Channel *Record::get_editing_channel() +{ + Batch *batch = get_editing_batch(); + return batch ? batch->channel : 0; +} + +ArrayList* Record::get_video_inputs() +{ + return default_asset->video_data && vdevice ? vdevice->get_inputs() : 0; +} + + +int64_t Record::timer_position() +{ + timer_lock->lock("RecordAudio::timer_positioin"); + int samplerate = default_asset->sample_rate; + int64_t result = timer.get_scaled_difference(samplerate); + result += session_sample_offset; + timer_lock->unlock(); + return result; +} + +void Record::reset_position(int64_t position) +{ + timer_lock->lock("RecordAudio::reset_position"); + session_sample_offset = position; + device_sample_offset = adevice ? adevice->current_position() : 0; + timer.update(); + timer_lock->unlock(); +} + +void Record::update_position() +{ + int64_t position = sync_position(); + reset_position(position); + current_frame = current_sample = 0; + audio_time = video_time = -1.; +} + +int64_t Record::adevice_position() +{ + timer_lock->lock("RecordAudio::adevice_position"); + int64_t result = adevice->current_position(); + result += session_sample_offset - device_sample_offset; + timer_lock->unlock(); + return result; +} + +int64_t Record::sync_position() +{ + int64_t sync_position = -1; + double sync_time = -1.; + double next_audio_time = -1.; + double next_video_time = -1.; + + if( current_frame == 0 && current_sample == 0 ) { + audio_time = video_time = -1.; + reset_position(0); + } + switch( SESSION->record_positioning ) { + case RECORD_POS_TIMESTAMPS: + if( default_asset->video_data && record_video ) + next_video_time = vdevice->get_timestamp(); + if( default_asset->audio_data && record_audio ) { + next_audio_time = + !adevice->is_monitoring() || writing_file > 0 ? + adevice->get_itimestamp() : + adevice->get_otimestamp(); + } + if( next_audio_time >= 0. ) + sync_time = next_audio_time; + else if( next_video_time > 0. ) + sync_time = next_video_time; + if( sync_time >= 0. ) { + sync_position = sync_time * default_asset->sample_rate; + } + if( sync_position >= 0 ) break; + case RECORD_POS_DEVICE: + if( default_asset->audio_data && record_audio ) + sync_position = adevice_position(); + if( sync_position > 0 ) break; + case RECORD_POS_SOFTWARE: + sync_position = timer_position(); + if( sync_position > 0 ) break; + case RECORD_POS_SAMPLE: + default: + sync_position = current_sample; + break; + } + + if( next_video_time < 0. ) + next_video_time = current_frame / default_asset->frame_rate; + if( next_video_time > video_time ) + video_time = next_video_time; + if( next_audio_time < 0. ) + next_audio_time = (double)sync_position / default_asset->sample_rate; + if( next_audio_time > audio_time ) + audio_time = next_audio_time; + + return sync_position; +} + + +void Record::adevice_drain() +{ + if( adevice && record_audio ) { + adevice->stop_audio(0); + adevice->start_recording(); + } +} + + +#define dbmsg if( debug ) printf +void Record::resync() +{ + dropped = behind = 0; + int64_t audio_position = sync_position(); + double audiotime = (double) audio_position / default_asset->sample_rate; + double diff = video_time - audiotime; + const int debug = 0; + dbmsg("resync video %f audio %f/%f diff %f", + video_time, audiotime, audio_time, diff); + if( fabs(diff) > 5. ) { + dbmsg(" drain audio"); +// jam job dvb file tesing, comment next line + record_channel->drain_audio(); + } + else if( diff > 0. ) { + int64_t delay = (int64_t)(1000.0 * diff); + dbmsg(" delay " _LDv(8), delay); + if( delay > 500 ) { + video_time = audio_time = -1.; + delay = 500; + } + if( delay > 1 ) + Timer::delay(delay); + } + else { + behind = (int)(-diff * default_asset->frame_rate); + dbmsg(" behind %d", behind); + if( behind > 1 && fill_underrun_frames ) { + dropped = behind > 3 ? 3 : behind-1; + dbmsg(" dropped %d", dropped); + } + } + dbmsg("\n"); + int frames = dropped + 1; + current_frame += frames; + total_frames += frames; + record_gui->update_video(dropped, behind); + if( dropped > 0 && drop_overrun_frames ) + if( vdevice ) vdevice->drop_frames(dropped); +} + + +void Record::close_audio_input() +{ + adevice_lock->lock(); + if( adevice ) { + adevice->close_all(); + delete adevice; + adevice = 0; + } + adevice_lock->unlock(); +} + +void Record::close_video_input() +{ + vdevice_lock->lock(); + if( vdevice ) { + vdevice->close_all(); + delete vdevice; + vdevice = 0; + } + vdevice_lock->unlock(); +} + +void Record::close_input_devices() +{ + close_audio_input(); + close_video_input(); +} + +void Record::stop_audio_thread() +{ + if( record_audio ) { + record_audio->stop_recording(); + delete record_audio; + record_audio = 0; + } + close_audio_input(); + recording = 0; +} + +void Record::stop_video_thread() +{ + if( record_video ) { + record_video->stop_recording(); + delete record_video; + record_video = 0; + } + close_video_input(); + capturing = 0; + single_frame = 0; +} + +void Record::stop_input_threads() +{ + pause_lock->lock("Record::stop_input_threads"); + stop_skimming(); + stop_playback(); + stop_audio_thread(); + stop_video_thread(); + pause_lock->unlock(); +} + +void Record::stop_playback() +{ + if( record_monitor ) + record_monitor->stop_playback(); +} + +void Record::open_audio_input() +{ + close_audio_input(); + adevice_lock->lock(); +// Create devices + if( default_asset->audio_data ) + adevice = new AudioDevice(mwindow); +// Configure audio + if( adevice ) { + int sw_pos = SESSION->record_positioning == RECORD_POS_SOFTWARE; + adevice->set_software_positioning(sw_pos); + adevice->open_input(SESSION->aconfig_in, SESSION->vconfig_in, + default_asset->sample_rate, get_fragment_samples(), + default_asset->channels, SESSION->real_time_record); + adevice->start_recording(); + adevice->open_monitor(SESSION->playback_config->aconfig,monitor_audio); + adevice->set_vdevice(vdevice); + if( vdevice ) vdevice->set_adevice(adevice); + } + adevice_lock->unlock(); +} + +void Record::open_video_input() +{ + close_video_input(); + vdevice_lock->lock(); + if( default_asset->video_data ) + vdevice = new VideoDevice(mwindow); +// Initialize video + if( vdevice ) { + vdevice->set_quality(default_asset->jpeg_quality); + vdevice->open_input(SESSION->vconfig_in, video_x, video_y, + video_zoom, default_asset->frame_rate); +// Get configuration parameters from device probe + color_model = vdevice->get_best_colormodel(default_asset); + master_channel->copy_usage(vdevice->channel); + picture->copy_usage(vdevice->picture); + vdevice->set_field_order(reverse_interlace); + vdevice->set_adevice(adevice); + if( adevice ) adevice->set_vdevice(vdevice); + set_dev_channel(get_current_channel()); + } + vdevice_lock->unlock(); +} + +void Record::start_audio_thread() +{ + open_audio_input(); + if( !record_audio ) { + total_samples = current_sample = 0; audio_time = -1.; + record_audio = new RecordAudio(mwindow,this); + record_audio->arm_recording(); + record_audio->start_recording(); + } + recording = 1; +} + +void Record::start_video_thread() +{ + open_video_input(); + if( !record_video ) { + total_frames = current_frame = 0; video_time = -1.; + record_video = new RecordVideo(mwindow,this); + record_video->arm_recording(); + record_video->start_recording(); + } + capturing = 1; +} + +void Record::start_input_threads() +{ + behind = 0; + input_threads_pausing = 0; + if( default_asset->audio_data ) + start_audio_thread(); + if( default_asset->video_data ) + start_video_thread(); + start_skimming(); +} + +void Record::pause_input_threads() +{ + pause_lock->lock("Record::pause_input_threads"); + input_threads_pausing = 1; + stop_skimming(); + adevice_lock->lock(); + vdevice_lock->lock(); + if( record_audio ) + record_audio->pause_recording(); + if( record_video ) + record_video->pause_recording(); +} + +void Record::resume_input_threads() +{ + if( record_audio ) + record_audio->resume_recording(); + if( record_video ) + record_video->resume_recording(); + vdevice_lock->unlock(); + adevice_lock->unlock(); + input_threads_pausing = 0; + start_skimming(); + pause_lock->unlock(); + +} + +int Record::start_toc() +{ + if( !mwindow->edl->session->record_realtime_toc ) return 0; + Batch *batch = get_current_batch(); + Asset *asset = batch->asset; + char source_filename[BCTEXTLEN], toc_path[BCTEXTLEN]; + IndexFile::get_index_filename(source_filename, + mwindow->preferences->index_directory, + toc_path, asset->path,".toc"); + if( default_asset->video_data ) + return vdevice->start_toc(asset->path, toc_path); + if( default_asset->audio_data ) + return adevice->start_toc(asset->path, toc_path); + return -1; +} + +int Record::start_record(int fd) +{ + start_toc(); + if( default_asset->video_data ) + return vdevice->start_record(fd); + if( default_asset->audio_data ) + return adevice->start_record(fd); + return -1; +} + +void Record::start_writing_file() +{ + if( !writing_file ) { + written_frames = 0; + written_samples = 0; + do_video = File::supports_video(default_asset->format); + do_audio = File::supports_audio(default_asset->format); + if( single_frame ) do_audio = 0; + if( !do_video && single_frame ) + single_frame = 0; + else if( !open_output_file() ) + writing_file = default_asset->format == FILE_MPEG ? -1 : 1; + if( do_audio && record_audio && writing_file > 0 ) { + int buffer_samples = get_buffer_samples(); + record_audio->set_write_buffer_samples(buffer_samples); + file->start_audio_thread(buffer_samples, FILE_RING_BUFFERS); + } + if( do_video && record_video && writing_file > 0 ) { + int disk_frames = SESSION->video_write_length; + int cmodel = vdevice->get_best_colormodel(default_asset); + int cpress = vdevice->is_compressed(1, 0); + file->start_video_thread(disk_frames, cmodel, FILE_RING_BUFFERS, cpress); + } + if( writing_file < 0 ) { + int fd = file->record_fd(); + if( fd >= 0 ) + start_record(fd); + } + if( writing_file ) { + record_gui->start_flash_batch(); + if( SESSION->record_sync_drives ) { + drivesync = new DriveSync(); + drivesync->start(); + } + } + } + update_position(); +} + +int Record::stop_record() +{ + if( default_asset->video_data ) + return vdevice->stop_record(); + if( default_asset->audio_data ) + return adevice->stop_record(); + return -1; +} + +void Record::flush_buffer() +{ + if( record_audio ) + record_audio->flush_buffer(); + if( record_video ) + record_video->flush_buffer(); +} + +void Record::stop_writing_file() +{ + record_gui->stop_flash_batch(); + if( !writing_file ) return; + if( writing_file > 0 ) + flush_buffer(); + if( writing_file < 0 ) + stop_record(); + close_output_file(); + writing_file = 0; + Asset *asset = current_asset(); + asset->audio_length = written_samples; + asset->video_length = written_frames; + record_gui->flash_batch(); + if( drivesync ) { + drivesync->done = 1; + delete drivesync; + drivesync = 0; + } +} + +void Record::stop_writing() +{ + if( writing_file ) { + pause_input_threads(); + stop_writing_file(); + resume_input_threads(); + } +} + + +void Record::start_cron_thread() +{ + if( cron_active() < 0 ) { + delete record_thread; + record_thread = 0; + } + if( !record_thread ) { + record_thread = new RecordThread(mwindow,this); + record_thread->start(); + record_gui->disable_batch_buttons(); + record_gui->update_cron_status(_("Running")); + } +} + +void Record::stop_cron_thread(const char *msg) +{ + if( record_thread ) { + delete record_thread; + record_thread = 0; + record_gui->enable_batch_buttons(); + record_gui->update_cron_status(msg); + } +} + +void Record::set_power_off(int value) +{ + power_off = value; + record_gui->power_off->update(value); +} + +int Record::set_video_picture() +{ + if( default_asset->video_data && vdevice ) + vdevice->set_picture(picture); + return 0; +} + +void Record::set_translation(int x, int y) +{ + video_x = x; + video_y = y; + if(default_asset->video_data && vdevice) + vdevice->set_translation(video_x, video_y); +} + +int Record::set_channel(Channel *channel) +{ + if( !channel ) return 1; +printf("set_channel %s\n",channel->title); + if( record_channel->set(channel) ) return 1; + RecordMonitorGUI *window = record_monitor->window; + window->lock_window("Record::set_channel"); + window->channel_picker->channel_text->update(channel->title); + window->unlock_window(); + return 0; +} + +int Record::set_channel_no(int chan_no) +{ + if( chan_no < 0 || chan_no >= channeldb->size() ) return 1; + Channel *channel = channeldb->get(chan_no); + return set_channel(channel); +} + +int Record::channel_down() +{ + Channel *channel = get_current_channel(); + if( !channel || !channeldb->size() ) return 1; + int n = channeldb->number_of(channel); + if( n < 0 ) return 1; + if( --n < 0 ) n = channeldb->size() - 1; + return set_channel_no(n); +} + +int Record::channel_up() +{ + Channel *channel = get_current_channel(); + if( !channel || !channeldb->size() ) return 1; + int n = channeldb->number_of(channel); + if( n < 0 ) return 1; + if( ++n >= channeldb->size() ) n = 0; + return set_channel_no(n); +} + +int Record::set_channel_name(const char *name) +{ + Channel *channel = 0; + int ch = 0, nch = channeldb->size(); + while( ch < nch ) { + channel = channeldb->get(ch); + if( channel && !channel->cstrcmp(name) ) break; + ++ch; + } + if( ch >= nch || !channel ) return 1; + return set_channel(channel); +} + +void Record::set_batch_channel_no(int chan_no) +{ + Channel *channel = channeldb->get(chan_no); + if( channel ) { + Batch *batch = get_editing_batch(); + if( batch ) batch->channel = channel; + record_gui->batch_source->update(channel->title); + } +} + +void Record::set_dev_channel(Channel *channel) +{ + // should be tuner device, not vdevice + if( channel && vdevice && + (!this->channel || *channel != *this->channel) ) { + current_channel = channel; + if( this->channel ) delete this->channel; + this->channel = new Channel(channel); + vdevice->set_channel(channel); + int strk = !SESSION->decode_subtitles ? -1 : SESSION->subtitle_number; + vdevice->set_captioning(strk); + set_video_picture(); +printf("new channel %s, has_signal=%d\n",channel->title,vdevice->has_signal()); + total_frames = 0; + total_samples = 0; + total_time.update(); + } +} + +/* widget holds xlock and set_channel pauses rendering, deadlock */ +/* use a background thread to update channel after widget sets it */ +RecordChannel::RecordChannel(Record *record) + : Thread(1, 0, 0) +{ + this->record = record; + change_channel = new Condition(0,"RecordSetChannel::change_channel"); + channel_lock = new Condition(1,"Record::channel_lock",1); + new_channel = 0; + Thread::start(); +} + +RecordChannel::~RecordChannel() +{ + done = 1; + change_channel->unlock(); + Thread::join(); + delete change_channel; + delete channel_lock; +} + +int RecordChannel::set(Channel *channel) +{ + if( !channel || new_channel ) return 1; + new_channel = channel; + channel_lock->lock(); // block has_signal + change_channel->unlock(); // resume channel changer thread + return 0; +} + +void RecordChannel::drain_audio() +{ + if( !audio_drain ) { + audio_drain = 1; + change_channel->unlock(); + } +} + +void RecordChannel::run() +{ + done = 0; + while( !done ) { + change_channel->lock(); + if( done ) break; + if( !new_channel && !audio_drain ) continue; + record->pause_input_threads(); + if( new_channel ) { + record->set_dev_channel(new_channel); + record->record_gui->reset_video(); + record->record_gui->reset_audio(); + audio_drain = 0; + } + if( audio_drain ) { + audio_drain = 0; + record->adevice_drain(); + } + record->update_position(); + record->resume_input_threads(); + if( new_channel ) { + new_channel = 0; + channel_lock->unlock(); + } + } +} + +int Record::has_signal() +{ + record_channel->channel_lock->lock("Record::has_signal"); + record_channel->channel_lock->unlock(); + vdevice_lock->lock(); + int result = vdevice ? vdevice->has_signal() : 0; + vdevice_lock->unlock(); + return result; +} + +int Record::create_channeldb(ArrayList *channeldb) +{ + return vdevice ? vdevice->create_channeldb(channeldb) : 1; +} + + +void Record::set_audio_monitoring(int mode) +{ + update_position(); + monitor_audio = mode; + if( record_audio ) + record_audio->set_monitoring(mode); + record_gui->lock_window("Record::set_audio_monitoring"); + if( record_gui->monitor_audio ) + record_gui->monitor_audio->update(mode); + record_gui->unlock_window(); +} + +void Record::set_audio_metering(int mode) +{ + metering_audio = mode; + record_gui->lock_window("Record::set_audio_metering"); + if( record_gui->meter_audio ) + record_gui->meter_audio->update(mode); + record_gui->unlock_window(); + RecordMonitorGUI *window = record_monitor->window; + window->lock_window("Record::set_audio_metering 1"); + window->resize_event(window->get_w(),window->get_h()); + window->unlock_window(); +} + + +void Record::set_video_monitoring(int mode) +{ + monitor_video = mode; + record_gui->lock_window("Record::set_video_monitoring"); + if( record_gui->monitor_video ) + record_gui->monitor_video->update(mode); + record_gui->flush(); + record_gui->unlock_window(); +} + +int Record::get_time_format() +{ + return SESSION->time_format; +} + +int Record::set_record_mode(int value) +{ + Batch *batch = get_editing_batch(); + batch->record_mode = value; + record_gui->update_batches(); + return 0; +} + +int Record::check_batch_complete() +{ + int result = 0; + batch_lock->lock(); + if( writing_file && cron_active() > 0 ) { + Batch *batch = get_current_batch(); + if( batch && batch->recorded > 0 && + batch->record_mode == RECORD_TIMED && + current_display_position() >= batch->record_duration ) { + batch->recorded = -1; + batch->enabled = 0; + record_gui->update_batches(); + record_thread->batch_timed_lock->unlock(); + result = 1; + } + } + batch_lock->unlock(); + return result; +} + + +int Record::skimming(void *vp, int track) +{ + return ((Record*)vp)->skimming(track); +} + +int Record::skimming(int track) +{ + int64_t framenum; uint8_t *tdat; int mw, mh; + if( !vdevice ) return -1; + if( vdevice->get_thumbnail(track, framenum, tdat, mw, mh) ) return 1; + int pid, width, height; double framerate; + if( vdevice->get_video_info(track, pid, framerate, width, height, 0) ) return 1; + if( !framerate ) return 1; + return skim_thread->skim(pid,framenum,framerate, tdat,mw,mh,width,height); +} + +void Record::start_skimming() +{ + if( commercial_check && vdevice && !skimming_active ) { + skimming_active = 1; + skim_thread->start(mwindow->commercials); + vdevice->set_skimming(0, 0, skimming, this); + } +} + +void Record::stop_skimming() +{ + if( skimming_active && vdevice ) { + skimming_active = 0; + vdevice->set_skimming(0, 0, 0, 0); + skim_thread->stop(); + } +} + +void Record::update_skimming(int v) +{ + if( (commercial_check=v) != 0 ) + start_skimming(); + else + stop_skimming(); +} + +RecordRemoteHandler::RecordRemoteHandler(RemoteControl *remote_control) + : RemoteHandler(remote_control->gui, GREEN) +{ +} + +RecordRemoteHandler::~RecordRemoteHandler() +{ +} + +void Record:: +display_video_text(int x, int y, const char *text, int font, + int bg_color, int color, int alpha, double secs, double scale) +{ +// gotta have a window for resources + record_monitor->window-> + display_video_text(x, y, text, font, + bg_color, color, alpha, secs, scale); +} + +void Record:: +display_cut_icon(int x, int y) +{ + VFrame *cut_icon = *mwindow->theme->get_image_set("commercial"); + double scale = 3; + x += cut_icon->get_w()*scale; y += cut_icon->get_h()*scale; + display_vframe(cut_icon, x, y, 200, 1.0, scale); +} + +DeviceDVBInput *Record:: +dvb_device() +{ + DeviceDVBInput *dvb_dev = !vdevice ? 0 : + (DeviceDVBInput *)vdevice->mpeg_device(); + if( !dvb_dev ) dvb_dev = !adevice ? 0 : + (DeviceDVBInput *)adevice->mpeg_device(); + return dvb_dev; +} + +#if 0 + +void Record:: +display_vframe(VFrame *in, int x, int y, int alpha, double secs, double scale) +{ + if( !channel ) return; + if( !vdevice || vdevice->in_config->driver != CAPTURE_DVB ) return; + DeviceDVBInput *dvb_input = dvb_device(); + if( !dvb_input ) return 1; + zmpeg3_t *fd = dvb_input->get_src(); + if( !fd ) return; + scale *= fd->video_height(channel->video_stream) / 1080.; + int ww = in->get_w() * scale, hh = in->get_h() * scale; + VFrame out(ww, hh, BC_YUV444P); + BC_WindowBase::get_cmodels()->transfer(out.get_rows(), in->get_rows(), + out.get_y(), out.get_u(), out.get_v(), + in->get_y(), in->get_u(), in->get_v(), + 0, 0, in->get_w(), in->get_h(), + 0, 0, out.get_w(), out.get_h(), + in->get_color_model(), out.get_color_model(), 0, + in->get_bytes_per_line(), ww); + int sz = ww * hh; uint8_t *yuv = out.get_data(), aimg[sz]; + uint8_t *yp = yuv, *up = yuv+sz, *vp = yuv+2*sz, *ap = 0; + if( alpha > 0 ) memset(ap=aimg, alpha, sz); + else if( alpha < 0 ) ap = yp; + fd->display_subtitle(channel->video_stream, 1, 1, + yp,up,vp,ap, x,y,ww,hh, 0, secs*1000); + dvb_input->put_src(); +} + +void Record:: +undisplay_vframe() +{ + if( !channel ) return; + if( !vdevice || vdevice->in_config->driver != CAPTURE_DVB ) return; + DeviceDVBInput *dvb_input = dvb_device(); + if( !dvb_input ) return 1; + zmpeg3_t *fd = dvb_input->get_src(); + if( !fd ) return; + fd->delete_subtitle(channel->video_stream, 1, 1); + dvb_input->put_src(); +} + +#else + +void Record:: +display_vframe(VFrame *in, int x, int y, int alpha, double secs, double scale) +{ + record_monitor->display_vframe(in, x, y, alpha, secs, scale); +} + +void Record:: +undisplay_vframe() +{ + record_monitor->undisplay_vframe(); +} + +#endif + +int Record:: +display_channel_info() +{ + if( !channel ) return 1; + if( !vdevice || vdevice->in_config->driver != CAPTURE_DVB ) return 1; + DeviceDVBInput *dvb_input = dvb_device(); + if( !dvb_input ) return 1; + int elem = channel->element; + time_t tt; time(&tt); + struct tm ttm; localtime_r(&tt,&ttm); + int result = 1; + char text[BCTEXTLEN]; + zmpeg3_t *fd = dvb_input->get_src(); + if( !fd ) return 1; + for( int ord=0, i=0; ord<0x80; ) { + char *cp = text; + int len = fd->dvb.get_chan_info(elem,ord,i++,cp,BCTEXTLEN-2); + if( len < 0 ) { ++ord; i = 0; continue; } + struct tm stm = ttm, etm = ttm; + char wday[4], *bp = cp; cp += len; + if( sscanf(bp, "%02d:%02d:%02d-%02d:%02d:%02d", + &stm.tm_hour, &stm.tm_min, &stm.tm_sec, + &etm.tm_hour, &etm.tm_min, &etm.tm_sec) != 6 ) continue; + while( bp= et ) continue; + int major=0, minor=0; + fd->dvb.get_channel(elem, major, minor); + char chan[64]; sprintf(chan, "%3d.%1d", major, minor); + for( i=0; i<5 && chan[i]!=0; ++i ) *bp++ = chan[i]; + while( bp=60 && *bp==' ') ) { + if( ++n >= 10 ) break; + *(lp=bp) = '\n'; + } + } + *bp = 0; + while( bp > text && *--bp == '\n' ) *bp = 0; + result = 0; + break; + } + dvb_input->put_src(); + if( !result ) + display_video_text(20, 20, text, + BIGFONT, WHITE, BLACK, 0, 3., 1.); + return result; +} + +int Record:: +display_channel_schedule() +{ + if( !channel ) return 1; + if( !vdevice || vdevice->in_config->driver != CAPTURE_DVB ) return 1; + DeviceDVBInput *dvb_input = dvb_device(); + if( !dvb_input ) return 1; + int elem = channel->element; + time_t tt; time(&tt); + struct tm ttm; localtime_r(&tt,&ttm); + char text[BCTEXTLEN]; + zmpeg3_t *fd = dvb_input->get_src(); + if( !fd ) return 1; + RecordSchedule channel_schedule; + for( int ord=0, i=0; ord<0x80; ) { + char *cp = text; + int len = fd->dvb.get_chan_info(elem,ord,i++,cp,BCTEXTLEN-2); + if( len < 0 ) { ++ord; i = 0; continue; } + struct tm stm = ttm, etm = ttm; + char wday[4], *bp = cp; cp += len; + if( sscanf(bp, "%02d:%02d:%02d-%02d:%02d:%02d", + &stm.tm_hour, &stm.tm_min, &stm.tm_sec, + &etm.tm_hour, &etm.tm_min, &etm.tm_sec) != 6 ) continue; + while( bp= et ) continue; + if( bp > text )*--bp = 0; + channel_schedule.append(new RecordScheduleItem(st, text)); + } + dvb_input->put_src(); + + channel_schedule.sort_times(); + char *cp = text, *ep = cp+sizeof(text)-1; + for( int i=0, n=0; ititle; cp= 12 ) break; + } + *cp = 0; + while( cp > text && *--cp == '\n' ) *cp = 0; + + display_video_text(20, 20, text, + BIGFONT, WHITE, BLACK, 0, 3., 1.); + return 0; +} + +void Record::clear_keybfr() +{ + keybfr[0] = 0; + undisplay_vframe(); +} + +void Record::add_key(int ch) +{ + int n = 0; + while( n<(int)sizeof(keybfr)-2 && keybfr[n] ) ++n; + keybfr[n++] = ch; keybfr[n] = 0; + display_video_text(20, 20, keybfr, + BIGFONT, WHITE, BLACK, 0, 3., 2.); +} + +int RecordRemoteHandler::remote_process_key(RemoteControl *remote_control, int key) +{ + Record *record = remote_control->mwindow_gui->record; + return record->remote_process_key(remote_control, key); +} + +int Record::remote_process_key(RemoteControl *remote_control, int key) +{ + int ch = key; + + switch( key ) { + case KPENTER: + if( last_key == KPENTER ) { + set_channel_name(keybfr); + clear_keybfr(); + break; + } + ch = '.'; // fall through + case '0': if( last_key == '0' && ch == '0' ) { + clear_keybfr(); + break; + } + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + add_key(ch); + break; + //case UP: case DOWN: case LEFT: case RIGHT: + //case KPPLAY: case KPBACK: case KPFORW: + case KPRECD: case 'c': // start capture, mark endpoint + if( !deletions ) { + start_commercial_capture(); + if( adevice ) + set_play_gain(0.075); + } + else { + mark_commercial_capture(DEL_MARK); + blink_status->update(); + } + display_cut_icon(10,20); + break; + case KPSTOP: case 'd': // end capture, start cutting + remote_control->set_color(YELLOW); + stop_commercial_capture(1); + break; + case KPAUSE: case 'x': // ignore current commercial + mark_commercial_capture(DEL_SKIP); + break; + case KPBACK: case 'a': // toggle mute audio + if( !monitor_audio ) { set_mute_gain(1); set_play_gain(1); } + set_audio_monitoring(monitor_audio ? 0 : 1); + break; + case 'm': // toggle metering audio + set_audio_metering(metering_audio ? 0 : 1); + break; + case KPPLAY: case 's': // ignore previous endpoint + mark_commercial_capture(DEL_OOPS); + break; + case KPFWRD: case KPSLASH: + display_channel_info(); + break; + case KPMAXW: case KPSTAR: + display_channel_schedule(); + break; + case KPCHUP: case KPPLUS: + channel_up(); + break; + case KPCHDN: case KPMINUS: + channel_down(); + break; + case 'f': { + Canvas *canvas = record_monitor->window->canvas; + if( !canvas->get_fullscreen() ) + canvas->start_fullscreen(); + else + canvas->stop_fullscreen(); + break; } + default: + return -1; + } + + last_key = key; + return 1; +} + + +int Record::start_commercial_capture() +{ + if( deletions != 0 ) return 1; + Channel *channel = get_current_channel(); + if( !channel ) return 1; + int track = channel->video_stream; + int pid = vdevice->get_video_pid(track); + if( pid < 0 ) return 1; + time_t st; time(&st); + struct tm stm; localtime_r(&st, &stm); + char file_path[BCTEXTLEN]; + sprintf(file_path,"/tmp/del%04d%02d%02d-%02d%02d%02d.ts", + stm.tm_year+1900, stm.tm_mon+1, stm.tm_mday, + stm.tm_hour, stm.tm_min, stm.tm_sec); + commercial_fd = open(file_path,O_RDWR+O_CREAT,0666); + if( commercial_fd < 0 ) return 1; + if( vdevice->start_record(commercial_fd, 0x800000) ) { + close(commercial_fd); + commercial_fd = -1; + return 1; + } + commercial_start_time = st; + printf("del to %s\n", file_path); + deletions = new Deletions(pid, file_path); + mark_commercial_capture(DEL_START); + blink_status->start(); + return 0; +} + +int Record::mark_commercial_capture(int action) +{ + if( deletions == 0 ) return 1; + double time = vdevice->get_timestamp(); + printf("dele %f\n", time); + deletions->append(new Dele(time, action)); + return 0; +} + +void Record::remote_fill_color(int color) +{ + mwindow->gui->remote_control->fill_color(color); +} + +void Record::set_status_color(int color) +{ + status_color = color; + remote_fill_color(status_color); +} + +void Record::set_mute_gain(double gain) +{ + gain = (mute_gain = gain) * play_gain; + if( adevice ) adevice->set_play_gain(gain); +} + +void Record::set_play_gain(double gain) +{ + gain = mute_gain * (play_gain = gain); + if( adevice ) adevice->set_play_gain(gain); +} + +int Record::stop_commercial_capture(int run_job) +{ + if( deletions == 0 ) return 1; + if( run_job ) blink_status->stop(); + set_play_gain(1); + + vdevice->stop_record(); + commercial_start_time = -1; + if( commercial_fd >= 0 ) { + close(commercial_fd); + commercial_fd = -1; + } + int result = -1; + if( run_job ) { + char del_filename[BCTEXTLEN]; + strcpy(del_filename, deletions->file_path()); + strcpy(strrchr(del_filename, '.'),".del"); + if( !deletions->write_dels(del_filename) ) { + int pid = spawn("cutads %s",del_filename); + if( pid > 0 ) { + cut_pids.append(pid); + cutads_status->start_waiting(); + result = 0; + } + } + } + else { + remove_file(deletions->filepath); + } + delete deletions; deletions = 0; + return result; +} + +int Record:: +spawn(const char *fmt, ...) +{ + char exe_path[BCTEXTLEN], cmd[BCTEXTLEN]; + get_exe_path(exe_path); + va_list ap; va_start(ap, fmt); + int n = snprintf(cmd, sizeof(cmd), "exec %s/", exe_path); + vsnprintf(cmd+n, sizeof(cmd)-n, fmt, ap); va_end(ap); + pid_t pid = vfork(); + if( pid < 0 ) return -1; + if( pid > 0 ) return pid; + char *const argv[4] = { (char*) "/bin/sh", (char*) "-c", cmd, 0 }; + execvp(argv[0], &argv[0]); + return -1; +} + +int Record:: +commercial_jobs() +{ + for( int i=0; irecord = record; + wait_lock = new Condition(0,"RecordCutAdsStatus::wait_lock",0); + done = 0; + start(); +} + +RecordCutAdsStatus:: +~RecordCutAdsStatus() +{ + if( running() ) { + done = 1; + wait_lock->unlock(); + cancel(); + join(); + } + delete wait_lock; +} + +void RecordCutAdsStatus:: +start_waiting() +{ + wait_lock->unlock(); +} + +void RecordCutAdsStatus:: +run() +{ + int status, pgrp = getpgrp(); + while(!done) { + wait_lock->lock("RecordCutAdsStatus::run"); + if( done ) break; + if( !record->commercial_jobs() ) continue; + record->set_status_color(YELLOW); + while( !done ) { + enable_cancel(); + waitpid(-pgrp, &status, 0); + disable_cancel(); + if( !record->commercial_jobs() ) break; + } + record->set_status_color(GREEN); + } +} + +void RecordBlinkStatus:: +remote_color(int color) +{ + record->remote_fill_color(color); +} + +void RecordBlinkStatus:: +update() +{ + timer.update(); +} + +void RecordBlinkStatus:: +start() +{ + done = 0; + update(); + Thread::start(); +} + +void RecordBlinkStatus:: +stop() +{ + done = 1; + if( running() ) { + cancel(); + join(); + } +} + +RecordBlinkStatus:: +RecordBlinkStatus(Record *record) + : Thread(1, 0, 0) +{ + this->record = record; +} + +RecordBlinkStatus:: +~RecordBlinkStatus() +{ + stop(); +} + +void RecordBlinkStatus:: +run() +{ + int color = YELLOW; + while( !done ) { + remote_color(color); + enable_cancel(); + usleep(500000); + disable_cancel(); + color ^= YELLOW ^ BLUE; + if( timer.get_difference() > 10*60*1000 ) { // 10 minites + record->stop_commercial_capture(0); + done = 1; + } + } + remote_color(record->status_color); +} +