mirror of
https://github.com/tuxbox-fork-migrations/recycled-ni-neutrino.git
synced 2025-08-26 15:02:50 +02:00
Origin commit data
------------------
Branch: ni/coolstream
Commit: fcb392908b
Author: Jacek Jendrzej <overx300@gmail.com>
Date: 2021-10-27 (Wed, 27 Oct 2021)
------------------
No further description and justification available within origin commit message!
------------------
This commit was generated by Migit
2509 lines
75 KiB
C++
2509 lines
75 KiB
C++
/*
|
|
Neutrino-GUI - DBoxII-Project
|
|
|
|
Copyright (C) 2001 Steffen Hehn 'McClean'
|
|
Copyright (C) 2011 CoolStream International Ltd
|
|
Copyright (C) 2011-2014 Stefan Seyfried
|
|
|
|
License: GPLv2
|
|
|
|
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;
|
|
|
|
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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/un.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/vfs.h>
|
|
#include <errno.h>
|
|
#include <math.h>
|
|
|
|
#include <global.h>
|
|
#include <neutrino.h>
|
|
#include <gui/filebrowser.h>
|
|
#include <gui/movieplayer.h>
|
|
#include <gui/nfs.h>
|
|
#include <gui/widget/hintbox.h>
|
|
#include <gui/widget/msgbox.h>
|
|
#include <gui/widget/mountchooser.h>
|
|
#include <daemonc/remotecontrol.h>
|
|
#include <system/setting_helpers.h>
|
|
#include <system/fsmounter.h>
|
|
#include <system/helpers.h>
|
|
#include <system/hddstat.h>
|
|
|
|
#include <driver/display.h>
|
|
#include <driver/record.h>
|
|
#include <driver/radiotext.h>
|
|
#include <driver/streamts.h>
|
|
#include <driver/abstime.h>
|
|
#include <zapit/capmt.h>
|
|
#include <zapit/channel.h>
|
|
#include <zapit/getservices.h>
|
|
#include <zapit/zapit.h>
|
|
#include <zapit/client/zapittools.h>
|
|
#include <eitd/sectionsd.h>
|
|
#include <timerdclient/timerdclient.h>
|
|
#include <cs_api.h>
|
|
|
|
extern "C" {
|
|
#include <libavformat/avformat.h>
|
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 0, 100)
|
|
#include <libavcodec/bsf.h>
|
|
#endif
|
|
}
|
|
|
|
#if (LIBAVCODEC_VERSION_MAJOR > 55)
|
|
#define av_free_packet av_packet_unref
|
|
#else
|
|
#define av_packet_unref av_free_packet
|
|
#endif
|
|
|
|
class CStreamRec : public CRecordInstance, OpenThreads::Thread
|
|
{
|
|
private:
|
|
AVFormatContext *ifcx;
|
|
AVFormatContext *ofcx;
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 100)
|
|
AVBitStreamFilterContext *bsfc;
|
|
#else
|
|
AVBSFContext *bsfc;
|
|
#endif
|
|
bool stopped;
|
|
bool interrupt;
|
|
time_t time_started;
|
|
int stream_index;
|
|
|
|
void GetPids(CZapitChannel * channel);
|
|
void FillMovieInfo(CZapitChannel * channel, APIDList & apid_list);
|
|
bool Start();
|
|
|
|
void Close();
|
|
bool Open(CZapitChannel * channel);
|
|
void run();
|
|
void WriteHeader(uint32_t duration);
|
|
public:
|
|
CStreamRec(const CTimerd::RecordingInfo * const eventinfo, std::string &dir, bool timeshift = false, bool stream_vtxt_pid = false, bool stream_pmt_pid = false, bool stream_subtitle_pids = false);
|
|
~CStreamRec();
|
|
record_error_msg_t Record();
|
|
bool Stop(bool remove_event = true);
|
|
static int Interrupt(void * data);
|
|
};
|
|
|
|
/* TODO:
|
|
* nextRecording / pending recordings - needs testing
|
|
* check/fix askUserOnTimerConflict gui/timerlist.cpp -> getOverlappingTimers lib/timerdclient/timerdclient.cpp
|
|
* check/test is it needed at all and is it possible to use different demux / another recmap for timeshift
|
|
*/
|
|
|
|
extern CRemoteControl * g_RemoteControl; /* neutrino.cpp */
|
|
|
|
extern "C" {
|
|
#include <driver/genpsi.h>
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
CRecordInstance::CRecordInstance(const CTimerd::RecordingInfo * const eventinfo, std::string &dir, bool timeshift, bool stream_vtxt_pid, bool stream_pmt_pid, bool stream_subtitle_pids )
|
|
{
|
|
channel_id = eventinfo->channel_id;
|
|
epg_id = eventinfo->epg_id;
|
|
epgTitle = eventinfo->epgTitle;
|
|
epg_time = eventinfo->epg_starttime;
|
|
apidmode = eventinfo->apids;
|
|
recording_id = eventinfo->eventID;
|
|
|
|
if (apidmode == TIMERD_APIDS_CONF)
|
|
apidmode = g_settings.recording_audio_pids_default;
|
|
|
|
StreamVTxtPid = stream_vtxt_pid;
|
|
StreamPmtPid = stream_pmt_pid;
|
|
StreamSubtitlePids = stream_subtitle_pids;
|
|
|
|
Directory = dir;
|
|
autoshift = timeshift;
|
|
move_ts2rec = false;
|
|
numpids = 0;
|
|
|
|
cMovieInfo = new CMovieInfo();
|
|
recMovieInfo = new MI_MOVIE_INFO();
|
|
record = NULL;
|
|
rec_stop_msg = g_Locale->getText(LOCALE_RECORDING_STOP);
|
|
}
|
|
|
|
CRecordInstance::~CRecordInstance()
|
|
{
|
|
allpids.APIDs.clear();
|
|
recMovieInfo->audioPids.clear();
|
|
delete recMovieInfo;
|
|
delete cMovieInfo;
|
|
delete record;
|
|
}
|
|
|
|
bool CRecordInstance::SaveXml()
|
|
{
|
|
int fd;
|
|
std::string xmlfile = std::string(filename) + ".xml";
|
|
|
|
if ((fd = open(xmlfile.c_str(), O_CREAT | O_TRUNC | O_SYNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) {
|
|
std::string extMessage;
|
|
cMovieInfo->encodeMovieInfoXml(&extMessage, recMovieInfo);
|
|
write(fd, extMessage.c_str(), extMessage.size() /*strlen(info)*/);
|
|
fdatasync(fd);
|
|
close(fd);
|
|
return true;
|
|
}
|
|
perror(xmlfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
void CRecordInstance::WaitRecMsg(time_t StartTime, time_t WaitTime)
|
|
{
|
|
return;
|
|
while (time(0) < StartTime + WaitTime)
|
|
usleep(100000);
|
|
}
|
|
|
|
#if HAVE_ARM_HARDWARE || HAVE_MIPS_HARDWARE
|
|
void recordingFailureHelper(void *data)
|
|
{
|
|
CRecordInstance *inst = (CRecordInstance *) data;
|
|
std::string errormsg = std::string(g_Locale->getText(LOCALE_RECORDING_FAILED)) + "\n" + std::string(inst->GetFileName());
|
|
CHintBox hintBox(LOCALE_MESSAGEBOX_INFO, errormsg.c_str());
|
|
hintBox.paint();
|
|
sleep(3);
|
|
hintBox.hide();
|
|
}
|
|
#endif
|
|
|
|
//NI
|
|
#if 0
|
|
int CRecordInstance::GetStatus()
|
|
{
|
|
if (record)
|
|
return record->GetStatus();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
record_error_msg_t CRecordInstance::Start(CZapitChannel * channel)
|
|
{
|
|
time_t msg_start_time = time(0);
|
|
CHintBox hintBox(LOCALE_MESSAGEBOX_INFO, g_Locale->getText(LOCALE_RECORDING_START));
|
|
if ((!(autoshift && g_settings.timeshift_auto)) && g_settings.recording_startstop_msg)
|
|
hintBox.paint();
|
|
|
|
//NI wakeup_hdd(Directory.c_str());
|
|
|
|
std::string tsfile = std::string(filename) + ".ts";
|
|
printf("%s: file %s vpid %x apid %x\n", __FUNCTION__, tsfile.c_str(), allpids.PIDs.vpid, apids[0]);
|
|
|
|
int fd = open(tsfile.c_str(), O_CREAT | O_TRUNC | O_SYNC | O_RDWR | O_LARGEFILE | O_CLOEXEC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if(fd < 0) {
|
|
perror(tsfile.c_str());
|
|
hintBox.hide();
|
|
return RECORD_INVALID_DIRECTORY;
|
|
}
|
|
|
|
SaveXml();
|
|
|
|
CGenPsi psi;
|
|
numpids = 0;
|
|
if (allpids.PIDs.vpid != 0){
|
|
psi.addPid(allpids.PIDs.vpid, recMovieInfo->VideoType == CHANNEL_MPEG4 ? EN_TYPE_AVC : recMovieInfo->VideoType == CHANNEL_HEVC ? EN_TYPE_HEVC : EN_TYPE_VIDEO, 0);
|
|
if (allpids.PIDs.pcrpid && (allpids.PIDs.pcrpid != allpids.PIDs.vpid)) {
|
|
psi.addPid(allpids.PIDs.pcrpid, EN_TYPE_PCR, 0);
|
|
apids[numpids++]=allpids.PIDs.pcrpid;
|
|
}
|
|
}
|
|
for (unsigned int i = 0; i < recMovieInfo->audioPids.size(); i++) {
|
|
apids[numpids++] = recMovieInfo->audioPids[i].AudioPid;
|
|
switch (channel->getAudioChannel(i)->audioChannelType) {
|
|
case CZapitAudioChannel::EAC3:
|
|
psi.addPid(recMovieInfo->audioPids[i].AudioPid, EN_TYPE_AUDIO_EAC3, 0, channel->getAudioChannel(i)->description.c_str());
|
|
break;
|
|
case CZapitAudioChannel::AAC:
|
|
psi.addPid(recMovieInfo->audioPids[i].AudioPid, EN_TYPE_AUDIO_AAC, 0, channel->getAudioChannel(i)->description.c_str());
|
|
break;
|
|
case CZapitAudioChannel::AACPLUS:
|
|
psi.addPid(recMovieInfo->audioPids[i].AudioPid, EN_TYPE_AUDIO_AACP, 0, channel->getAudioChannel(i)->description.c_str());
|
|
break;
|
|
default:
|
|
psi.addPid(recMovieInfo->audioPids[i].AudioPid, EN_TYPE_AUDIO, (recMovieInfo->audioPids[i].atype == CZapitAudioChannel::AC3), channel->getAudioChannel(i)->description.c_str());
|
|
break;
|
|
}
|
|
|
|
if (numpids >= REC_MAX_APIDS)
|
|
break;
|
|
}
|
|
if ((StreamVTxtPid) && (allpids.PIDs.vtxtpid != 0) && (numpids < REC_MAX_APIDS)){
|
|
apids[numpids++] = allpids.PIDs.vtxtpid;
|
|
psi.addPid(allpids.PIDs.vtxtpid, EN_TYPE_TELTEX, 0, channel->getTeletextLang());
|
|
}
|
|
if (StreamSubtitlePids){
|
|
for (int i = 0 ; i < (int)channel->getSubtitleCount() ; ++i) {
|
|
CZapitAbsSub* s = channel->getChannelSub(i);
|
|
if (s->thisSubType == CZapitAbsSub::DVB) {
|
|
if(i>9)//max sub pids
|
|
break;
|
|
if (numpids >= REC_MAX_APIDS)
|
|
break;
|
|
|
|
CZapitDVBSub* sd = reinterpret_cast<CZapitDVBSub*>(s);
|
|
apids[numpids++] = sd->pId;
|
|
psi.addPid( sd->pId, EN_TYPE_DVBSUB, 0, sd->ISO639_language_code.c_str() );
|
|
}
|
|
}
|
|
|
|
}
|
|
psi.genpsi(fd);
|
|
|
|
#if 0
|
|
if ((StreamPmtPid) && (allpids.PIDs.pmtpid != 0) && (numpids < REC_MAX_APIDS))
|
|
apids[numpids++] = allpids.PIDs.pmtpid;
|
|
#endif
|
|
|
|
if (record == NULL)
|
|
{
|
|
#if HAVE_ARM_HARDWARE || HAVE_MIPS_HARDWARE
|
|
record = new cRecord(channel->getRecordDemux(), g_settings.recording_bufsize_dmx * 1024 * 1024, g_settings.recording_bufsize * 1024 * 1024);
|
|
record->setFailureCallback(&recordingFailureHelper, this);
|
|
#else
|
|
record = new cRecord(channel->getRecordDemux() /*RECORD_DEMUX*/);
|
|
#endif
|
|
}
|
|
|
|
record->Open();
|
|
|
|
if(!record->Start(fd, (unsigned short ) allpids.PIDs.vpid, (unsigned short *) apids, numpids, channel_id)) {
|
|
record->Stop();
|
|
delete record;
|
|
record = NULL;
|
|
close(fd);
|
|
unlink(tsfile.c_str());
|
|
std::string xmlfile = std::string(filename) + ".xml";
|
|
unlink(xmlfile.c_str());
|
|
hintBox.hide();
|
|
return RECORD_FAILURE;
|
|
}
|
|
|
|
printf("CRecordInstance::Start: fe %d demux %d\n", frontend->getNumber(), channel->getRecordDemux());
|
|
if(!autoshift)
|
|
CFEManager::getInstance()->lockFrontend(frontend, channel);//FIXME testing
|
|
|
|
start_time = time(0);
|
|
|
|
CCamManager::getInstance()->Start(channel->getChannelID(), CCamManager::RECORD);
|
|
|
|
//CVFD::getInstance()->ShowIcon(VFD_ICON_CAM1, true);
|
|
WaitRecMsg(msg_start_time, 2);
|
|
hintBox.hide();
|
|
return RECORD_OK;
|
|
}
|
|
|
|
bool CRecordInstance::Stop(bool remove_event)
|
|
{
|
|
char buf[FILENAMEBUFFERSIZE+4]={0};
|
|
|
|
struct stat test;
|
|
snprintf(buf,sizeof(buf), "%s.xml", filename);
|
|
if(stat(buf, &test) == 0){
|
|
recMovieInfo->clear();
|
|
snprintf(buf,sizeof(buf), "%s.ts", filename);
|
|
recMovieInfo->file.Name = buf;
|
|
cMovieInfo->loadMovieInfo(recMovieInfo);//restore user bookmark
|
|
}
|
|
|
|
time_t end_time = time(0);
|
|
recMovieInfo->length = (end_time - start_time + 30) / 60;
|
|
|
|
CHintBox hintBox(LOCALE_MESSAGEBOX_INFO, rec_stop_msg.c_str());
|
|
if ((!(autoshift && g_settings.timeshift_auto)) && g_settings.recording_startstop_msg)
|
|
hintBox.paint();
|
|
|
|
printf("%s: channel %" PRIx64 " recording_id %d\n", __func__, channel_id, recording_id);
|
|
printf("%s: file %s.ts\n", __FUNCTION__, filename);
|
|
SaveXml();
|
|
/* Stop do close fd - if started */
|
|
record->Stop();
|
|
|
|
|
|
CCamManager::getInstance()->Stop(channel_id, CCamManager::RECORD);
|
|
|
|
if(!autoshift)
|
|
CFEManager::getInstance()->unlockFrontend(frontend, true);//FIXME testing
|
|
|
|
if (autoshift && move_ts2rec)
|
|
CMoviePlayerGui::getInstance().moveTimeshift();
|
|
else if (autoshift && g_settings.timeshift_delete)
|
|
CMoviePlayerGui::getInstance().deleteTimeshift();
|
|
|
|
if(recording_id && remove_event) {
|
|
g_Timerd->stopTimerEvent(recording_id);
|
|
recording_id = 0;
|
|
}
|
|
//CVFD::getInstance()->ShowIcon(VFD_ICON_CAM1, false);
|
|
#ifdef ENABLE_GRAPHLCD
|
|
cGLCD::unlockIcon(cGLCD::REC);
|
|
#endif
|
|
WaitRecMsg(end_time, 2);
|
|
hintBox.hide();
|
|
return true;
|
|
}
|
|
|
|
bool CRecordInstance::Update()
|
|
{
|
|
APIDList apid_list;
|
|
APIDList::iterator it;
|
|
bool update = false;
|
|
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if(channel == NULL) {
|
|
printf("%s: channel %" PRIx64 " not found!\n", __func__, channel_id);
|
|
return false;
|
|
}
|
|
|
|
if(channel->getVideoPid() != allpids.PIDs.vpid) {
|
|
Stop(false);
|
|
MakeFileName(channel);
|
|
GetPids(channel);
|
|
FilterPids(apid_list);
|
|
FillMovieInfo(channel, apid_list);
|
|
record_error_msg_t ret = Start(channel /*, apid_list*/);
|
|
if(ret == RECORD_OK) {
|
|
CCamManager::getInstance()->Start(channel_id, CCamManager::RECORD, true);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GetPids(channel);
|
|
FilterPids(apid_list);
|
|
|
|
for(it = apid_list.begin(); it != apid_list.end(); ++it) {
|
|
bool found = false;
|
|
for(unsigned int i = 0; i < numpids; i++) {
|
|
if(apids[i] == it->apid) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if(!found) {
|
|
update = true;
|
|
printf("%s: apid %x not found in recording pids\n", __FUNCTION__, it->apid);
|
|
if (numpids < REC_MAX_APIDS)
|
|
apids[numpids++] = it->apid;
|
|
|
|
record->AddPid(it->apid);
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if(allpids.APIDs[i].pid == it->apid) {
|
|
AUDIO_PIDS audio_pids;
|
|
|
|
audio_pids.AudioPid = allpids.APIDs[i].pid;
|
|
audio_pids.AudioPidName = allpids.APIDs[i].desc;
|
|
audio_pids.atype = allpids.APIDs[i].is_ac3 ? CZapitAudioChannel::AC3 : allpids.APIDs[i].is_aac ? CZapitAudioChannel::AAC : allpids.APIDs[i].is_eac3 ? CZapitAudioChannel::EAC3 : CZapitAudioChannel::MPEG;
|
|
audio_pids.selected = 0;
|
|
recMovieInfo->audioPids.push_back(audio_pids);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!update) {
|
|
printf("%s: no update needed\n", __FUNCTION__);
|
|
return false;
|
|
}
|
|
|
|
SaveXml();
|
|
CCamManager::getInstance()->Start(channel_id, CCamManager::RECORD, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CRecordInstance::GetPids(CZapitChannel * channel)
|
|
{
|
|
allpids.PIDs.vpid = channel->getVideoPid();
|
|
allpids.PIDs.vtxtpid = channel->getTeletextPid();
|
|
allpids.PIDs.pmtpid = channel->getPmtPid();
|
|
allpids.PIDs.selected_apid = channel->getAudioChannelIndex();
|
|
allpids.PIDs.pcrpid = channel->getPcrPid();
|
|
#if 0 // not needed
|
|
allpids.PIDs.privatepid = channel->getPrivatePid();
|
|
#endif
|
|
allpids.APIDs.clear();
|
|
for (uint32_t i = 0; i < channel->getAudioChannelCount(); i++) {
|
|
CZapitClient::responseGetAPIDs response;
|
|
response.pid = channel->getAudioPid(i);
|
|
strncpy(response.desc, channel->getAudioChannel(i)->description.c_str(), DESC_MAX_LEN - 1);
|
|
response.is_ac3 = response.is_aac = response.is_aache = response.is_eac3 = response.is_dts = response.is_dtshd = response.is_lpcm = 0;
|
|
if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::AC3) {
|
|
response.is_ac3 = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::AAC) {
|
|
response.is_aac = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::AACPLUS) {
|
|
response.is_aache = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::EAC3) {
|
|
response.is_eac3 = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::DTS) {
|
|
response.is_dts = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::DTSHD) {
|
|
response.is_dtshd = 1;
|
|
} else if (channel->getAudioChannel(i)->audioChannelType == CZapitAudioChannel::LPCM) {
|
|
response.is_lpcm = 1;
|
|
}
|
|
response.component_tag = channel->getAudioChannel(i)->componentTag;
|
|
allpids.APIDs.push_back(response);
|
|
}
|
|
ProcessAPIDnames();
|
|
}
|
|
|
|
void CRecordInstance::ProcessAPIDnames()
|
|
{
|
|
bool has_unresolved_ctags = false;
|
|
|
|
for(unsigned int count=0; count< allpids.APIDs.size(); count++) {
|
|
//printf("Neutrino: apid name= %s (%s) pid= %X\n", allpids.APIDs[count].desc, getISO639Description( allpids.APIDs[count].desc ), allpids.APIDs[count].pid);
|
|
if (allpids.APIDs[count].component_tag != 0xFF)
|
|
has_unresolved_ctags= true;
|
|
|
|
std::string tmp_desc = allpids.APIDs[count].desc;
|
|
if ( strlen( allpids.APIDs[count].desc ) == 3 ){
|
|
tmp_desc = getISO639Description( allpids.APIDs[count].desc );
|
|
}
|
|
|
|
if ( allpids.APIDs[count].is_ac3 && tmp_desc.find(" (AC3)"))
|
|
tmp_desc += " (AC3)";
|
|
else if (allpids.APIDs[count].is_aac && tmp_desc.find(" (AAC)"))
|
|
tmp_desc += " (AAC)";
|
|
else if (allpids.APIDs[count].is_eac3 && tmp_desc.find(" (EAC3)"))
|
|
tmp_desc += " (EAC3)";
|
|
if(!tmp_desc.empty()){
|
|
strncpy( allpids.APIDs[count].desc, tmp_desc.c_str(),DESC_MAX_LEN -1) ;
|
|
}
|
|
}
|
|
|
|
if(has_unresolved_ctags && (epg_id != 0)) {
|
|
CSectionsdClient::ComponentTagList tags;
|
|
if(CEitManager::getInstance()->getComponentTagsUniqueKey(epg_id, tags)) {
|
|
for(unsigned int i=0; i< tags.size(); i++) {
|
|
for(unsigned int j=0; j< allpids.APIDs.size(); j++) {
|
|
if(allpids.APIDs[j].component_tag == tags[i].componentTag) {
|
|
if(!tags[i].component.empty()) {
|
|
std::string tmp_desc2;
|
|
tmp_desc2 = tags[i].component;
|
|
if (allpids.APIDs[j].is_ac3 && tmp_desc2.find(" (AC3)"))
|
|
tmp_desc2 += " (AC3)";
|
|
else if (allpids.APIDs[j].is_aac && tmp_desc2.find(" (AAC)"))
|
|
tmp_desc2 += " (AAC)";
|
|
else if (allpids.APIDs[j].is_aache && tmp_desc2.find(" (AACP)"))
|
|
tmp_desc2 += " (AACP)";
|
|
else if (allpids.APIDs[j].is_eac3 && tmp_desc2.find(" (EAC3)"))
|
|
tmp_desc2 += " (EAC3)";
|
|
else if (allpids.APIDs[j].is_dts && tmp_desc2.find(" (DTS)"))
|
|
tmp_desc2 += " (DTS)";
|
|
else if (allpids.APIDs[j].is_dtshd && tmp_desc2.find(" (DTSHD)"))
|
|
tmp_desc2 += " (DTSHD)";
|
|
else if (allpids.APIDs[j].is_lpcm && tmp_desc2.find(" (LPCM)"))
|
|
tmp_desc2 += " (LPCM)";
|
|
|
|
if(!tmp_desc2.empty()){
|
|
strncpy(allpids.APIDs[j].desc, tmp_desc2.c_str(), DESC_MAX_LEN -1);
|
|
}
|
|
}
|
|
allpids.APIDs[j].component_tag = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
record_error_msg_t CRecordInstance::Record()
|
|
{
|
|
APIDList apid_list;
|
|
|
|
printf("%s: channel %" PRIx64 " recording_id %d\n", __func__, channel_id, recording_id);
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if(channel == NULL) {
|
|
printf("%s: channel %" PRIx64 " not found!\n", __func__, channel_id);
|
|
return RECORD_INVALID_CHANNEL;
|
|
}
|
|
|
|
record_error_msg_t ret = MakeFileName(channel);
|
|
if(ret != RECORD_OK)
|
|
return ret;
|
|
|
|
GetPids(channel);
|
|
FilterPids(apid_list);
|
|
FillMovieInfo(channel, apid_list);
|
|
|
|
ret = Start(channel /*, apid_list*/);
|
|
//FIXME recording_id (timerd eventID) is 0 means its user recording, in this case timer always added ?
|
|
if(ret == RECORD_OK && recording_id == 0) {
|
|
time_t now = time(NULL);
|
|
int record_end;
|
|
if (autoshift) {
|
|
record_end = now+g_settings.timeshift_hours*60*60;
|
|
} else {
|
|
record_end = now+g_settings.record_hours*60*60;
|
|
if (g_settings.recording_epg_for_end) {
|
|
int pre=0, post=0;
|
|
CEPGData epgData;
|
|
if (CEitManager::getInstance()->getActualEPGServiceKey(channel_id, &epgData )) {
|
|
g_Timerd->getRecordingSafety(pre, post);
|
|
if (epgData.epg_times.startzeit > 0)
|
|
record_end = epgData.epg_times.startzeit + epgData.epg_times.dauer + post;
|
|
}
|
|
}
|
|
}
|
|
recording_id = g_Timerd->addImmediateRecordTimerEvent(channel_id, now, record_end, epg_id, epg_time, apidmode);
|
|
printf("%s: channel %" PRIx64 " -> timer eventID %d\n", __func__, channel_id, recording_id);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void CRecordInstance::FilterPids(APIDList & apid_list)
|
|
{
|
|
apid_list.clear();
|
|
|
|
// assume smallest apid ist std apid
|
|
if (apidmode & TIMERD_APIDS_STD) {
|
|
uint32_t apid_min=UINT_MAX;
|
|
uint32_t apid_min_idx=0;
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].pid < apid_min && !allpids.APIDs[i].is_ac3 && !allpids.APIDs[i].is_eac3) {
|
|
apid_min = allpids.APIDs[i].pid;
|
|
apid_min_idx = i;
|
|
}
|
|
}
|
|
if (apid_min != UINT_MAX) {
|
|
APIDDesc a = {apid_min, apid_min_idx, false};
|
|
apid_list.push_back(a);
|
|
}
|
|
}
|
|
if (apidmode & TIMERD_APIDS_ALT) {
|
|
uint32_t apid_min=UINT_MAX;
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].pid < apid_min && !allpids.APIDs[i].is_ac3 && !allpids.APIDs[i].is_eac3) {
|
|
apid_min = allpids.APIDs[i].pid;
|
|
}
|
|
}
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].pid != apid_min && !allpids.APIDs[i].is_ac3 && !allpids.APIDs[i].is_eac3) {
|
|
APIDDesc a = {allpids.APIDs[i].pid, i, false};
|
|
apid_list.push_back(a);
|
|
}
|
|
}
|
|
}
|
|
if (apidmode & TIMERD_APIDS_AC3) {
|
|
bool ac3_found=false;
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].is_ac3 || allpids.APIDs[i].is_eac3) {
|
|
APIDDesc a = {allpids.APIDs[i].pid, i, true};
|
|
apid_list.push_back(a);
|
|
ac3_found=true;
|
|
}
|
|
}
|
|
// add non ac3 apid if ac3 not found
|
|
if (!(apidmode & TIMERD_APIDS_STD) && !ac3_found) {
|
|
uint32_t apid_min=UINT_MAX;
|
|
uint32_t apid_min_idx=0;
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].pid < apid_min && !allpids.APIDs[i].is_ac3 && !allpids.APIDs[i].is_eac3) {
|
|
apid_min = allpids.APIDs[i].pid;
|
|
apid_min_idx = i;
|
|
}
|
|
}
|
|
if (apid_min != UINT_MAX) {
|
|
APIDDesc a = {apid_min, apid_min_idx, false};
|
|
apid_list.push_back(a);
|
|
}
|
|
}
|
|
}
|
|
// no apid selected use standard
|
|
if (apid_list.empty() && !allpids.APIDs.empty()) {
|
|
uint32_t apid_min=UINT_MAX;
|
|
uint32_t apid_min_idx=0;
|
|
for(unsigned int i = 0; i < allpids.APIDs.size(); i++) {
|
|
if (allpids.APIDs[i].pid < apid_min && !allpids.APIDs[i].is_ac3 && !allpids.APIDs[i].is_eac3) {
|
|
apid_min = allpids.APIDs[i].pid;
|
|
apid_min_idx = i;
|
|
}
|
|
}
|
|
if (apid_min != UINT_MAX) {
|
|
APIDDesc a = {apid_min, apid_min_idx, false};
|
|
apid_list.push_back(a);
|
|
}
|
|
for(APIDList::iterator it = apid_list.begin(); it != apid_list.end(); ++it)
|
|
printf("Record APID 0x%X %d\n",it->apid, it->ac3);
|
|
}
|
|
}
|
|
|
|
void CRecordInstance::FillMovieInfo(CZapitChannel * channel, APIDList & apid_list)
|
|
{
|
|
std::string info1, info2;
|
|
|
|
recMovieInfo->clear();
|
|
|
|
std::string tmpstring = channel->getName();
|
|
|
|
if (tmpstring.empty())
|
|
recMovieInfo->channelName = "unknown";
|
|
else
|
|
recMovieInfo->channelName = tmpstring;
|
|
|
|
tmpstring = "not available";
|
|
if (epg_id != 0) {
|
|
CEPGData epgdata;
|
|
bool epg_ok = CEitManager::getInstance()->getEPGid(epg_id, epg_time, &epgdata);
|
|
if(!epg_ok){//if old epg_id removed check current epg_id
|
|
epg_ok = CEitManager::getInstance()->getActualEPGServiceKey(channel->getEpgID(), &epgdata );
|
|
|
|
if(epg_ok && !epgTitle.empty()){
|
|
std::string tmp_title = epgdata.title;
|
|
if(epgTitle != tmp_title)
|
|
epg_ok = false;
|
|
}
|
|
}
|
|
if (epg_ok) {
|
|
tmpstring = epgdata.title;
|
|
info1 = epgdata.info1;
|
|
info2 = epgdata.info2;
|
|
|
|
recMovieInfo->parentalLockAge = epgdata.fsk;
|
|
#ifdef FULL_CONTENT_CLASSIFICATION
|
|
if( !epgdata.contentClassification.empty() )
|
|
recMovieInfo->genreMajor = epgdata.contentClassification[0];
|
|
#else
|
|
if(epgdata.contentClassification)
|
|
recMovieInfo->genreMajor = epgdata.contentClassification;
|
|
#endif
|
|
|
|
recMovieInfo->length = epgdata.epg_times.dauer / 60;
|
|
|
|
printf("fsk:%d, Genre:%d, Dauer: %d\r\n",recMovieInfo->parentalLockAge,recMovieInfo->genreMajor,recMovieInfo->length);
|
|
} else if (!epgTitle.empty()) {//if old epg_id removed
|
|
tmpstring = epgTitle;
|
|
}
|
|
} else if (!epgTitle.empty()) {
|
|
tmpstring = epgTitle;
|
|
}
|
|
recMovieInfo->epgTitle = tmpstring;
|
|
recMovieInfo->channelId = channel->getChannelID();
|
|
recMovieInfo->epgInfo1 = info1;
|
|
recMovieInfo->epgInfo2 = info2;
|
|
recMovieInfo->epgId = epg_id;
|
|
recMovieInfo->mode = g_Zapit->getMode();
|
|
recMovieInfo->VideoPid = allpids.PIDs.vpid;
|
|
recMovieInfo->VideoType = channel->type;
|
|
|
|
AUDIO_PIDS audio_pids;
|
|
APIDList::iterator it;
|
|
for(unsigned int i= 0; i< allpids.APIDs.size(); i++) {
|
|
for(it = apid_list.begin(); it != apid_list.end(); ++it) {
|
|
if(allpids.APIDs[i].pid == it->apid) {
|
|
audio_pids.AudioPid = allpids.APIDs[i].pid;
|
|
audio_pids.AudioPidName = allpids.APIDs[i].desc;
|
|
audio_pids.atype = allpids.APIDs[i].is_ac3 ? CZapitAudioChannel::AC3 : allpids.APIDs[i].is_aac ? CZapitAudioChannel::AAC : allpids.APIDs[i].is_eac3 ? CZapitAudioChannel::EAC3 : CZapitAudioChannel::MPEG;
|
|
audio_pids.selected = (audio_pids.AudioPid == channel->getAudioPid()) ? 1 : 0;
|
|
recMovieInfo->audioPids.push_back(audio_pids);
|
|
}
|
|
}
|
|
}
|
|
/* FIXME sometimes no apid in xml ?? */
|
|
if(recMovieInfo->audioPids.empty() && !allpids.APIDs.empty()) {
|
|
int i = 0;
|
|
audio_pids.AudioPid = allpids.APIDs[i].pid;
|
|
audio_pids.AudioPidName = allpids.APIDs[i].desc;
|
|
audio_pids.atype = allpids.APIDs[i].is_ac3 ? CZapitAudioChannel::AC3 : allpids.APIDs[i].is_aac ? CZapitAudioChannel::AAC : allpids.APIDs[i].is_eac3 ? CZapitAudioChannel::EAC3 : CZapitAudioChannel::MPEG;
|
|
audio_pids.selected = 1;
|
|
recMovieInfo->audioPids.push_back(audio_pids);
|
|
}
|
|
recMovieInfo->VtxtPid = allpids.PIDs.vtxtpid;
|
|
}
|
|
|
|
record_error_msg_t CRecordInstance::MakeFileName(CZapitChannel * channel)
|
|
{
|
|
std::string ext_channel_name;
|
|
unsigned int pos;
|
|
|
|
//NI safe_mkdir(Directory.c_str());
|
|
if(check_dir(Directory.c_str())) {
|
|
/* check if Directory and network_nfs_recordingdir the same */
|
|
if(g_settings.network_nfs_recordingdir != Directory) {
|
|
//NI safe_mkdir(g_settings.network_nfs_recordingdir.c_str());
|
|
/* not the same, check network_nfs_recordingdir and return error if not ok */
|
|
if(check_dir(g_settings.network_nfs_recordingdir.c_str()))
|
|
return RECORD_INVALID_DIRECTORY;
|
|
/* fallback to g_settings.network_nfs_recordingdir */
|
|
Directory = g_settings.network_nfs_recordingdir;
|
|
}else{
|
|
return RECORD_INVALID_DIRECTORY;
|
|
}
|
|
}
|
|
|
|
// Create filename for recording
|
|
pos = Directory.size();
|
|
strcpy(filename, Directory.c_str());
|
|
|
|
if ((pos == 0) || (filename[pos - 1] != '/')) {
|
|
filename[pos] = '/';
|
|
pos++;
|
|
filename[pos] = '\0';
|
|
}
|
|
pos = strlen(filename);
|
|
|
|
ext_channel_name = channel->getName();
|
|
|
|
if (!(ext_channel_name.empty())) {
|
|
strcpy(&(filename[pos]), UTF8_TO_FILESYSTEM_ENCODING(ext_channel_name.c_str()));
|
|
ZapitTools::replace_char(&filename[pos]);
|
|
|
|
if (!autoshift && g_settings.recording_save_in_channeldir) {
|
|
struct stat statInfo;
|
|
int res = stat(filename,&statInfo);
|
|
if (res == -1) {
|
|
if (errno == ENOENT) {
|
|
res = safe_mkdir(filename);
|
|
if (res == 0)
|
|
strncat(filename,"/",FILENAMEBUFFERSIZE - strlen(filename) -1);
|
|
else
|
|
perror("[vcrcontrol] mkdir");
|
|
} else
|
|
perror("[vcrcontrol] stat");
|
|
} else
|
|
// directory exists
|
|
strncat(filename,"/",FILENAMEBUFFERSIZE - strlen(filename)-1);
|
|
} else
|
|
strncat(filename, "_",FILENAMEBUFFERSIZE - strlen(filename)-1);
|
|
}
|
|
|
|
pos = strlen(filename) - ((!autoshift && g_settings.recording_save_in_channeldir) ? 0 : (ext_channel_name.length() /*remove last "_"*/ +1));
|
|
|
|
std::string ext_file_name = g_settings.recording_filename_template;
|
|
MakeExtFileName(channel, ext_file_name);
|
|
strcpy(&(filename[pos]), UTF8_TO_FILESYSTEM_ENCODING(ext_file_name.c_str()));
|
|
|
|
//NI auto change record filename
|
|
std::string tsfile = std::string(filename) + ".ts";
|
|
if (access(tsfile, F_OK) == 0) {
|
|
printf("CRecordInstance::%s: ts file already found: %s\n", __func__, tsfile.c_str());
|
|
char newname[FILENAMEBUFFERSIZE] = {0};
|
|
bool changed = false;
|
|
for (unsigned int i = 2; i < 1000; i++) {
|
|
snprintf(newname, sizeof(newname), "%s_%03d", filename, i);
|
|
tsfile = std::string(newname) + ".ts";
|
|
if (access(tsfile, F_OK) != 0) {
|
|
snprintf(filename, sizeof(filename), "%s", newname);
|
|
changed = true;
|
|
printf("CRecordInstance::%s: ts file changed to: %s\n", __func__, tsfile.c_str());
|
|
break;
|
|
}
|
|
}
|
|
if (!changed)
|
|
printf("CRecordInstance::%s: can't change ts file: %s\n", __func__, tsfile.c_str());
|
|
}
|
|
|
|
if(autoshift)
|
|
strncat(filename, "_temp",FILENAMEBUFFERSIZE - strlen(filename)-1);
|
|
|
|
return RECORD_OK;
|
|
}
|
|
|
|
void CRecordInstance::StringReplace(std::string &str, const std::string search, const std::string rstr)
|
|
{
|
|
std::string::size_type ptr = 0;
|
|
std::string::size_type pos = 0;
|
|
while((ptr = str.find(search,pos)) != std::string::npos){
|
|
str.replace(ptr,search.length(),rstr);
|
|
pos = ptr + rstr.length();
|
|
}
|
|
}
|
|
|
|
void CRecordInstance::MakeExtFileName(CZapitChannel * channel, std::string &FilenameTemplate)
|
|
{
|
|
char buf[256];
|
|
|
|
// %C == channel, %T == title, %I == info1, %d == date, %t == time_t
|
|
if (FilenameTemplate.empty())
|
|
FilenameTemplate = "%C_%T_%d_%t";
|
|
|
|
time_t t = time(NULL);
|
|
strftime(buf,sizeof(buf),"%Y%m%d",localtime(&t));
|
|
StringReplace(FilenameTemplate,"%d",buf);
|
|
|
|
strftime(buf,sizeof(buf),"%H%M%S",localtime(&t));
|
|
StringReplace(FilenameTemplate,"%t",buf);
|
|
|
|
std::string channel_name = channel->getName();
|
|
if (!(channel_name.empty())) {
|
|
snprintf(buf, sizeof(buf),"%s", UTF8_TO_FILESYSTEM_ENCODING(channel_name.c_str()));
|
|
ZapitTools::replace_char(buf);
|
|
StringReplace(FilenameTemplate,"%C",buf);
|
|
}
|
|
else
|
|
StringReplace(FilenameTemplate,"%C","no_channel");
|
|
|
|
CShortEPGData epgdata;
|
|
if(CEitManager::getInstance()->getEPGidShort(epg_id, &epgdata)) {
|
|
if (!(epgdata.title.empty())) {
|
|
snprintf(buf, sizeof(buf),"%s", epgdata.title.c_str());
|
|
ZapitTools::replace_char(buf);
|
|
StringReplace(FilenameTemplate,"%T",buf);
|
|
}
|
|
else
|
|
StringReplace(FilenameTemplate,"%T","no_title");
|
|
|
|
if (!(epgdata.info1.empty())) {
|
|
snprintf(buf, sizeof(buf),"%s", epgdata.info1.c_str());
|
|
ZapitTools::replace_char(buf);
|
|
StringReplace(FilenameTemplate,"%I",buf);
|
|
}
|
|
else
|
|
StringReplace(FilenameTemplate,"%I","no_info");
|
|
} else {
|
|
StringReplace(FilenameTemplate,"%T","no_title");
|
|
StringReplace(FilenameTemplate,"%I","no_info");
|
|
}
|
|
}
|
|
|
|
void CRecordInstance::GetRecordString(std::string &str, std::string &dur)
|
|
{
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if(channel == NULL) {
|
|
printf("%s: channel %" PRIx64 " not found!\n", __func__, channel_id);
|
|
str = "Unknown channel : " + GetEpgTitle();
|
|
return;
|
|
}
|
|
char stime[15];
|
|
//NI int err = GetStatus();
|
|
strftime(stime, sizeof(stime), "%H:%M:%S ", localtime(&start_time));
|
|
time_t duration = (time(0) - start_time) / 60;
|
|
char dtime[22];
|
|
int h = duration / 60;
|
|
int m = duration - (h * 60);
|
|
snprintf(dtime, sizeof(dtime), "(%d %s %02d %s)", h, h == 1 ? g_Locale->getText(LOCALE_RECORDING_TIME_HOUR) : g_Locale->getText(LOCALE_RECORDING_TIME_HOURS),
|
|
m, g_Locale->getText(LOCALE_RECORDING_TIME_MIN));
|
|
str = stime + channel->getName() + ": " + GetEpgTitle(); //NI
|
|
dur = dtime;
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
CRecordManager * CRecordManager::manager = NULL;
|
|
OpenThreads::Mutex CRecordManager::sm;
|
|
|
|
CRecordManager::CRecordManager()
|
|
{
|
|
StreamVTxtPid = false;
|
|
StreamPmtPid = false;
|
|
StreamSubtitlePids = false;
|
|
StopSectionsd = false;
|
|
//recordingstatus = 0;
|
|
recmap.clear();
|
|
nextmap.clear();
|
|
durations.clear();
|
|
autoshift = false;
|
|
shift_timer = 0;
|
|
menu = NULL;
|
|
//NI
|
|
#if 0
|
|
check_timer = 0;
|
|
error_display = true;
|
|
warn_display = true;
|
|
#endif
|
|
}
|
|
|
|
CRecordManager::~CRecordManager()
|
|
{
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
CRecordInstance * inst = it->second;
|
|
inst->Stop();
|
|
delete inst;
|
|
}
|
|
recmap.clear();
|
|
for(nextmap_iterator_t it = nextmap.begin(); it != nextmap.end(); it++) {
|
|
/* Note: CTimerd::RecordingInfo is a class! => typecast to avoid destructor call */
|
|
delete[] (unsigned char *) (*it);
|
|
}
|
|
nextmap.clear();
|
|
durations.clear();
|
|
sm.lock();
|
|
manager = NULL;
|
|
sm.unlock();
|
|
}
|
|
|
|
CRecordManager * CRecordManager::getInstance()
|
|
{
|
|
sm.lock();
|
|
|
|
if(manager == NULL)
|
|
manager = new CRecordManager();
|
|
|
|
sm.unlock();
|
|
return manager;
|
|
}
|
|
|
|
CRecordInstance * CRecordManager::FindInstance(t_channel_id channel_id)
|
|
{
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
if (it->second->GetChannelId() == channel_id)
|
|
return it->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CRecordInstance * CRecordManager::FindInstanceID(int recid)
|
|
{
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
if (it->second->GetRecordingId() == recid)
|
|
return it->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
CRecordInstance * CRecordManager::FindTimeshift()
|
|
{
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
if (it->second->Timeshift())
|
|
return it->second;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool CRecordManager::CheckRecordingId_if_Timeshift(int recid)
|
|
{
|
|
if(recid > 0){
|
|
CRecordInstance * inst = FindInstanceID(recid);
|
|
if(inst){
|
|
return inst->Timeshift();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MI_MOVIE_INFO * CRecordManager::GetMovieInfo(t_channel_id channel_id, bool timeshift)
|
|
{
|
|
//FIXME copy MI_MOVIE_INFO ?
|
|
MI_MOVIE_INFO * mi = NULL;
|
|
|
|
mutex.lock();
|
|
CRecordInstance * inst = NULL;
|
|
if (timeshift)
|
|
inst = FindTimeshift();
|
|
if (inst == NULL)
|
|
inst = FindInstance(channel_id);
|
|
if (inst)
|
|
mi = inst->GetMovieInfo();
|
|
mutex.unlock();
|
|
return mi;
|
|
}
|
|
|
|
const std::string CRecordManager::GetFileName(t_channel_id channel_id, bool timeshift)
|
|
{
|
|
std::string filename;
|
|
CRecordInstance * inst = NULL;
|
|
if (timeshift)
|
|
inst = FindTimeshift();
|
|
if (inst == NULL)
|
|
inst = FindInstance(channel_id);
|
|
|
|
if(inst)
|
|
filename = inst->GetFileName();
|
|
return filename;
|
|
}
|
|
|
|
/* return record mode mask, for channel_id not 0, or global */
|
|
int CRecordManager::GetRecordMode(const t_channel_id channel_id)
|
|
{
|
|
int recmode = RECMODE_OFF;
|
|
mutex.lock();
|
|
if (channel_id == 0) {
|
|
/* we can have only one timeshift instance, if there are more - some is record */
|
|
if ((!autoshift && !recmap.empty()) || recmap.size() > 1)
|
|
recmode |= RECMODE_REC;
|
|
if (autoshift)
|
|
recmode |= RECMODE_TSHIFT;
|
|
} else {
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
if (it->second->GetChannelId() == channel_id) {
|
|
if (it->second->Timeshift())
|
|
recmode |= RECMODE_TSHIFT;
|
|
else
|
|
recmode |= RECMODE_REC;
|
|
}
|
|
}
|
|
}
|
|
mutex.unlock();
|
|
return recmode;
|
|
}
|
|
|
|
bool CRecordManager::Record(const t_channel_id channel_id, const char * dir, bool timeshift)
|
|
{
|
|
CTimerd::RecordingInfo eventinfo;
|
|
CEPGData epgData;
|
|
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if (!channel)
|
|
return false;
|
|
|
|
eventinfo.eventID = 0;
|
|
eventinfo.channel_id = channel_id;
|
|
if (CEitManager::getInstance()->getActualEPGServiceKey(channel->getEpgID(), &epgData )) {
|
|
eventinfo.epg_id = epgData.eventID;
|
|
eventinfo.epg_starttime = epgData.epg_times.startzeit;
|
|
strncpy(eventinfo.epgTitle, epgData.title.c_str(), EPG_TITLE_MAXLEN-1);
|
|
eventinfo.epgTitle[EPG_TITLE_MAXLEN-1]=0;
|
|
}
|
|
else {
|
|
eventinfo.epg_id = 0;
|
|
eventinfo.epg_starttime = 0;
|
|
strcpy(eventinfo.epgTitle, "");
|
|
}
|
|
eventinfo.apids = TIMERD_APIDS_CONF;
|
|
eventinfo.recordingDir[0] = 0;
|
|
|
|
return Record(&eventinfo, dir, timeshift);
|
|
}
|
|
|
|
bool CRecordManager::Record(const CTimerd::RecordingInfo * const eventinfo, const char * dir, bool timeshift)
|
|
{
|
|
CRecordInstance * inst = NULL;
|
|
record_error_msg_t error_msg = RECORD_OK;
|
|
/* for now, empty eventinfo.recordingDir means this is direct record, FIXME better way ?
|
|
* neutrino check if this channel_id already recording, may be not needed */
|
|
bool direct_record = timeshift || strlen(eventinfo->recordingDir) == 0;
|
|
|
|
printf("%s channel_id %" PRIx64 " epg: %" PRIx64 ", apidmode 0x%X\n", __func__,
|
|
eventinfo->channel_id, eventinfo->epg_id, eventinfo->apids);
|
|
|
|
if (g_settings.recording_type == CNeutrinoApp::RECORDING_OFF /* || IS_WEBCHAN(eventinfo->channel_id) */)
|
|
return false;
|
|
|
|
#if 1 // FIXME test
|
|
StopSectionsd = false;
|
|
if( !recmap.empty() )
|
|
StopSectionsd = true;
|
|
#endif
|
|
RunStartScript();
|
|
|
|
std::string newdir;
|
|
if(dir && strlen(dir))
|
|
newdir = std::string(dir);
|
|
else if(strlen(eventinfo->recordingDir))
|
|
newdir = std::string(eventinfo->recordingDir);
|
|
else
|
|
newdir = Directory;
|
|
|
|
mutex.lock();
|
|
if (IS_WEBCHAN(eventinfo->channel_id)) {
|
|
inst = new CStreamRec(eventinfo, newdir, timeshift, StreamVTxtPid, StreamPmtPid, StreamSubtitlePids);
|
|
error_msg = inst->Record();
|
|
if(error_msg == RECORD_OK) {
|
|
g_Zapit->setRecordMode(true);
|
|
recmap.insert(recmap_pair_t(inst->GetRecordingId(), inst));
|
|
if(timeshift)
|
|
autoshift = true;
|
|
} else {
|
|
delete inst;
|
|
}
|
|
} else if(recmap.size() < RECORD_MAX_COUNT) {
|
|
CFrontend * frontend = NULL;
|
|
if(CutBackNeutrino(eventinfo->channel_id, frontend)) {
|
|
|
|
inst = new CRecordInstance(eventinfo, newdir, timeshift, StreamVTxtPid, StreamPmtPid, StreamSubtitlePids);
|
|
|
|
inst->frontend = frontend;
|
|
error_msg = inst->Record();
|
|
if(error_msg == RECORD_OK) {
|
|
recmap.insert(recmap_pair_t(inst->GetRecordingId(), inst));
|
|
if(timeshift)
|
|
autoshift = true;
|
|
#if 0
|
|
// mimic old behavior for start/stop menu option chooser, still actual ?
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
if(eventinfo->channel_id == live_channel_id)
|
|
recordingstatus = 1;
|
|
#endif
|
|
#ifdef ENABLE_GRAPHLCD
|
|
cGLCD::Update();
|
|
#endif
|
|
} else {
|
|
delete inst;
|
|
}
|
|
} else if(!direct_record) {
|
|
CTimerd::RecordingInfo * evt = new CTimerd::RecordingInfo(*eventinfo);
|
|
printf("%s add %" PRIx64 " : %s to pending\n", __func__, evt->channel_id, evt->epgTitle);
|
|
nextmap.push_back((CTimerd::RecordingInfo *)evt);
|
|
}
|
|
} else
|
|
error_msg = RECORD_BUSY;
|
|
|
|
mutex.unlock();
|
|
|
|
if (error_msg == RECORD_OK) {
|
|
//NI
|
|
#if 0
|
|
if (check_timer == 0)
|
|
check_timer = g_RCInput->addTimer(5*1000*1000, false);
|
|
|
|
/* set flag to show record error if any */
|
|
error_display = true;
|
|
warn_display = true;
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
printf("[recordmanager] %s: error code: %d\n", __FUNCTION__, error_msg);
|
|
/* RestoreNeutrino must be called always if record start failed */
|
|
RunStopScript();
|
|
RestoreNeutrino();
|
|
|
|
/* FIXME show timeshift start error or not ? */
|
|
//if(!timeshift)
|
|
{
|
|
//FIXME: Use better error message
|
|
DisplayErrorMessage(g_Locale->getText(
|
|
error_msg == RECORD_BUSY ? LOCALE_STREAMING_BUSY :
|
|
error_msg == RECORD_INVALID_DIRECTORY ? LOCALE_STREAMING_DIR_NOT_WRITABLE :
|
|
LOCALE_STREAMING_WRITE_ERROR )); // UTF-8
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CRecordManager::StartAutoRecord()
|
|
{
|
|
printf("%s: starting to %s\n", __FUNCTION__, TimeshiftDirectory.c_str());
|
|
g_RCInput->killTimer (shift_timer);
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
return Record(live_channel_id, TimeshiftDirectory.c_str(), true);
|
|
}
|
|
|
|
bool CRecordManager::StopAutoRecord(bool lock)
|
|
{
|
|
printf("%s: autoshift %d\n", __FUNCTION__, autoshift);
|
|
|
|
g_RCInput->killTimer (shift_timer);
|
|
|
|
if (!autoshift)
|
|
return false;
|
|
|
|
if (lock)
|
|
mutex.lock();
|
|
|
|
CRecordInstance * inst = FindTimeshift();
|
|
if (inst)
|
|
{
|
|
StopInstance(inst);
|
|
CMoviePlayerGui::getInstance().stopTimeshift();
|
|
}
|
|
|
|
if (lock)
|
|
mutex.unlock();
|
|
|
|
return (inst != NULL);
|
|
}
|
|
|
|
void CRecordManager::StopAutoTimer()
|
|
{
|
|
g_RCInput->killTimer (shift_timer);
|
|
}
|
|
|
|
void CRecordManager::StartNextRecording()
|
|
{
|
|
CTimerd::RecordingInfo * eventinfo = NULL;
|
|
printf("%s: pending count %d\n", __func__, (int)nextmap.size());
|
|
|
|
for(nextmap_iterator_t it = nextmap.begin(); it != nextmap.end(); it++) {
|
|
eventinfo = *it;
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(eventinfo->channel_id);
|
|
if (channel && CFEManager::getInstance()->canTune(channel))
|
|
{
|
|
//MountDirectory(eventinfo->recordingDir);//FIXME in old neutrino startNextRecording commented
|
|
bool ret = Record(eventinfo);
|
|
if(ret) {
|
|
delete[] (unsigned char *) eventinfo;
|
|
it = nextmap.erase(it++);
|
|
if (it == nextmap.end())
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* return true, if there are any recording running for this channel id, or global if id is 0 */
|
|
bool CRecordManager::RecordingStatus(const t_channel_id channel_id)
|
|
{
|
|
bool ret = false;
|
|
|
|
mutex.lock();
|
|
|
|
if(channel_id) {
|
|
CRecordInstance * inst = FindInstance(channel_id);
|
|
ret = (inst != NULL);
|
|
} else
|
|
ret = !recmap.empty();
|
|
|
|
mutex.unlock();
|
|
return ret;
|
|
}
|
|
|
|
bool CRecordManager::TimeshiftOnly()
|
|
{
|
|
mutex.lock();
|
|
int count = recmap.size();
|
|
mutex.unlock();
|
|
return (autoshift && (count == 1));
|
|
}
|
|
|
|
/* FIXME no check for multi-tuner */
|
|
bool CRecordManager::SameTransponder(const t_channel_id channel_id)
|
|
{
|
|
bool same = true;
|
|
mutex.lock();
|
|
int count = recmap.size();
|
|
if(count) {
|
|
if(autoshift && count == 1)
|
|
same = true;
|
|
else {
|
|
recmap_iterator_t fit = recmap.begin();
|
|
t_channel_id id = fit->second->GetChannelId();
|
|
same = (SAME_TRANSPONDER(channel_id, id));
|
|
}
|
|
}
|
|
mutex.unlock();
|
|
return same;
|
|
}
|
|
|
|
void CRecordManager::StopInstance(CRecordInstance * inst, bool remove_event)
|
|
{
|
|
/* first erase - then stop, because Stop() reset recording_id to 0 */
|
|
recmap.erase(inst->GetRecordingId());
|
|
inst->Stop(remove_event);
|
|
|
|
if(inst->Timeshift())
|
|
autoshift = false;
|
|
|
|
delete inst;
|
|
}
|
|
|
|
bool CRecordManager::Stop(const t_channel_id channel_id)
|
|
{
|
|
printf("%s: %" PRIx64 "\n", __func__, channel_id);
|
|
|
|
mutex.lock();
|
|
|
|
/* FIXME stop all ? show list ? */
|
|
CRecordInstance * inst = FindInstance(channel_id);
|
|
if(inst != NULL)
|
|
StopInstance(inst);
|
|
else
|
|
printf("%s: channel %" PRIx64 " not recording\n", __func__, channel_id);
|
|
|
|
mutex.unlock();
|
|
|
|
StopPostProcess();
|
|
|
|
return (inst != NULL);
|
|
}
|
|
|
|
bool CRecordManager::IsRecording(const CTimerd::RecordingStopInfo * recinfo)
|
|
{
|
|
bool ret = false;
|
|
mutex.lock();
|
|
CRecordInstance * inst = FindInstanceID(recinfo->eventID);
|
|
if(inst != NULL && recinfo->eventID == inst->GetRecordingId())
|
|
ret = true;
|
|
mutex.unlock();
|
|
printf("[%s] eventID: %d, channel_id: 0x%" PRIx64 ", ret: %d\n", __func__, recinfo->eventID, recinfo->channel_id, ret);
|
|
return ret;
|
|
}
|
|
|
|
bool CRecordManager::Stop(const CTimerd::RecordingStopInfo * recinfo)
|
|
{
|
|
bool ret = false;
|
|
|
|
printf("%s: eventID %d channel_id %" PRIx64 "\n", __func__, recinfo->eventID, recinfo->channel_id);
|
|
|
|
mutex.lock();
|
|
|
|
CRecordInstance * inst = FindInstanceID(recinfo->eventID);
|
|
if(inst != NULL && recinfo->eventID == inst->GetRecordingId()) {
|
|
StopInstance(inst, false);
|
|
ret = true;
|
|
#ifdef ENABLE_GRAPHLCD
|
|
cGLCD::Update();
|
|
#endif
|
|
} else {
|
|
for(nextmap_iterator_t it = nextmap.begin(); it != nextmap.end(); it++) {
|
|
if((*it)->eventID == recinfo->eventID) {
|
|
printf("%s: removing pending eventID %d channel_id %" PRIx64 "\n", __func__, recinfo->eventID, recinfo->channel_id);
|
|
/* Note: CTimerd::RecordingInfo is a class! => typecast to avoid destructor call */
|
|
delete[] (unsigned char *) (*it);
|
|
nextmap.erase(it);
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(!ret)
|
|
printf("%s: eventID %d channel_id %" PRIx64 " : not found\n", __func__, recinfo->eventID, recinfo->channel_id);
|
|
|
|
mutex.unlock();
|
|
|
|
StopPostProcess();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CRecordManager::StopPostProcess()
|
|
{
|
|
RestoreNeutrino();
|
|
StartNextRecording();
|
|
RunStopScript();
|
|
//NI
|
|
#if 0
|
|
if(!RecordingStatus())
|
|
g_RCInput->killTimer(check_timer);
|
|
#endif
|
|
}
|
|
|
|
bool CRecordManager::Update(const t_channel_id channel_id)
|
|
{
|
|
mutex.lock();
|
|
|
|
CRecordInstance * inst = FindInstance(channel_id);
|
|
if(inst != NULL)
|
|
inst->Update();
|
|
else
|
|
printf("%s: channel %" PRIx64 " not recording\n", __func__, channel_id);
|
|
|
|
mutex.unlock();
|
|
return (inst != NULL);
|
|
}
|
|
|
|
int CRecordManager::handleMsg(const neutrino_msg_t msg, neutrino_msg_data_t data)
|
|
{
|
|
if(msg == NeutrinoMessages::EVT_ZAP_COMPLETE) {
|
|
g_RCInput->killTimer (shift_timer);
|
|
if (g_settings.timeshift_auto) {
|
|
int delay = g_settings.timeshift_auto;
|
|
shift_timer = g_RCInput->addTimer(delay*1000*1000, true);
|
|
g_InfoViewer->handleMsg(NeutrinoMessages::EVT_RECORDMODE, 1);
|
|
}
|
|
}
|
|
else if ((msg == NeutrinoMessages::EVT_TIMER)) {
|
|
if(data == shift_timer) {
|
|
shift_timer = 0;
|
|
if (!FindTimeshift())
|
|
StartAutoRecord();
|
|
return messages_return::handled;
|
|
}
|
|
//NI
|
|
#if 0
|
|
else if(data == check_timer) {
|
|
if(CNeutrinoApp::getInstance()->getMode() != NeutrinoModes::mode_standby) {
|
|
mutex.lock();
|
|
int have_err = 0;
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++)
|
|
have_err |= it->second->GetStatus();
|
|
mutex.unlock();
|
|
//printf("%s: check status: show err %d warn %d have_err %d\n", __FUNCTION__, error_display, warn_display, have_err); //FIXME
|
|
if (have_err) {
|
|
if ((have_err & REC_STATUS_OVERFLOW) && error_display) {
|
|
error_display = false;
|
|
warn_display = false;
|
|
DisplayErrorMessage(g_Locale->getText(LOCALE_STREAMING_OVERFLOW));
|
|
} else if (g_settings.recording_slow_warning && warn_display) {
|
|
warn_display = false;
|
|
ShowHint(LOCALE_MESSAGEBOX_INFO, LOCALE_STREAMING_SLOW, 700, 60, NEUTRINO_ICON_ERROR, NULL, CComponentsHeader::CC_BTN_EXIT);
|
|
}
|
|
}
|
|
return messages_return::handled;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
return messages_return::unhandled;
|
|
}
|
|
|
|
void CRecordManager::StartTimeshift()
|
|
{
|
|
if(g_RemoteControl->is_video_started)
|
|
{
|
|
std::string tmode = "timeshift_pause"; // already recording, pause
|
|
bool res = true;
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
// bool tstarted = false;
|
|
/* start temporary timeshift if enabled and not running, but dont start second record */
|
|
if (g_settings.timeshift_temp) {
|
|
if (!FindTimeshift()) {
|
|
res = StartAutoRecord();
|
|
tmode = "timeshift"; // record just started
|
|
// tstarted = true;
|
|
}
|
|
}
|
|
else if (!RecordingStatus(live_channel_id)) {
|
|
res = Record(live_channel_id);
|
|
tmode = "timeshift"; // record just started
|
|
}
|
|
|
|
if(res)
|
|
{
|
|
CMoviePlayerGui::getInstance().exec(NULL, tmode);
|
|
#if 0
|
|
/*
|
|
ShowMenu() moved to movieplayer.cpp
|
|
Function is called when stop key is pressed.
|
|
*/
|
|
if(g_settings.timeshift_temp && tstarted && autoshift)
|
|
ShowMenu();
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
int CRecordManager::exec(CMenuTarget* parent, const std::string & actionKey )
|
|
{
|
|
if (g_settings.recording_type == CNeutrinoApp::RECORDING_OFF)
|
|
return menu_return::RETURN_REPAINT;
|
|
|
|
if(parent)
|
|
parent->hide();
|
|
|
|
if(actionKey == "StopAll")
|
|
{
|
|
char rec_msg[256];
|
|
char rec_msg1[256];
|
|
int records = recmap.size();
|
|
|
|
snprintf(rec_msg1, sizeof(rec_msg1)-1, "%s", g_Locale->getText(LOCALE_RECORDINGMENU_MULTIMENU_ASK_STOP_ALL));
|
|
snprintf(rec_msg, sizeof(rec_msg)-1, rec_msg1, records);
|
|
if(ShowMsg(LOCALE_SHUTDOWN_RECORDING_QUERY, rec_msg,
|
|
CMsgBox::mbrYes, CMsgBox::mbYes | CMsgBox::mbNo, NULL, 450, 30) == CMsgBox::mbrYes)
|
|
{
|
|
snprintf(rec_msg1, sizeof(rec_msg1)-1, "%s", g_Locale->getText(LOCALE_RECORDINGMENU_MULTIMENU_INFO_STOP_ALL));
|
|
|
|
int i = 0;
|
|
mutex.lock();
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++)
|
|
{
|
|
CRecordInstance * inst = it->second;
|
|
t_channel_id channel_id = inst->GetChannelId();
|
|
|
|
snprintf(rec_msg, sizeof(rec_msg)-1, rec_msg1, records-i, records);
|
|
inst->SetStopMessage(rec_msg);
|
|
|
|
printf("CRecordManager::exec(ExitAll line %d) found channel %" PRIx64 " recording_id %d\n", __LINE__, channel_id, inst->GetRecordingId());
|
|
g_Timerd->stopTimerEvent(inst->GetRecordingId());
|
|
i++;
|
|
}
|
|
mutex.unlock();
|
|
}
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
} else if(actionKey == "Record")
|
|
{
|
|
printf("[neutrino] direct record\n");
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
|
|
bool tostart = true;
|
|
CRecordInstance * inst = FindInstance(live_channel_id);
|
|
if (inst) {
|
|
std::string title, duration;
|
|
inst->GetRecordString(title, duration);
|
|
title += " " + duration;
|
|
tostart = (ShowMsg(LOCALE_RECORDING_IS_RUNNING, title.c_str(),
|
|
CMsgBox::mbrYes, CMsgBox::mbYes | CMsgBox::mbNo, NULL, 450, DEFAULT_TIMEOUT) == CMsgBox::mbrYes);
|
|
}
|
|
//NI
|
|
if (g_settings.recording_startstop_msg) {
|
|
int ret = ShowMsg(LOCALE_RECORDING_END, g_Locale->getText(LOCALE_RECORDING_END_TEXT),
|
|
g_settings.recording_epg_for_end ? CMsgBox::mbrYes : CMsgBox::mbrNo, CMsgBox::mbYes | CMsgBox::mbNo | CMsgBox::mbCancel, NULL, 450, 20, true);
|
|
if (ret == CMsgBox::mbrCancel)
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
else
|
|
g_settings.recording_epg_for_end = (ret == CMsgBox::mbrYes);
|
|
|
|
cHddStat::getInstance()->statOnce();
|
|
if (cHddStat::getInstance()->getPercent() > g_settings.recording_fill_warning)
|
|
{
|
|
printf("[neutrino] fill is %d%% (warn %d%%)\n", cHddStat::getInstance()->getPercent(), g_settings.recording_fill_warning);
|
|
char txt[1024];
|
|
snprintf(txt, sizeof(txt)-1, g_Locale->getText(LOCALE_RECORDING_FILL_TEXT), cHddStat::getInstance()->getPercent());
|
|
|
|
ret = ShowMsg(LOCALE_HDD_STATFS, txt,
|
|
CMsgBox::mbrYes, CMsgBox::mbYes | CMsgBox::mbNo, NULL, 450, 20, true);
|
|
if (ret == CMsgBox::mbrNo)
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
}
|
|
}
|
|
if (tostart) {
|
|
CRecordManager::getInstance()->Record(live_channel_id);
|
|
|
|
if(!g_InfoViewer->is_visible) // show Infoviewer
|
|
CNeutrinoApp::getInstance()->showInfo();
|
|
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
}
|
|
} else if(actionKey == "Timeshift")
|
|
{
|
|
StartTimeshift();
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
} else if(actionKey == "Timeshift2Record")
|
|
{
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++)
|
|
{
|
|
CRecordInstance *inst = it->second;
|
|
if (inst->Timeshift())
|
|
{
|
|
inst->move_ts2rec = !inst->move_ts2rec;
|
|
CMenuItem* item = menu->getItem(menu->getSelected());
|
|
CMenuForwarder *f = static_cast<CMenuForwarder*>(item);
|
|
f->setInfoIconRight(inst->move_ts2rec ? NEUTRINO_ICON_MARKER_DIALOG_OK : NEUTRINO_ICON_MARKER_DIALOG_OFF);
|
|
}
|
|
}
|
|
return menu_return::RETURN_REPAINT;
|
|
} else if(actionKey == "Stop_record")
|
|
{
|
|
if (!CRecordManager::getInstance()->RecordingStatus())
|
|
{
|
|
ShowHint(LOCALE_MAINMENU_RECORDING_STOP, LOCALE_RECORDINGMENU_RECORD_IS_NOT_RUNNING, 450, 2);
|
|
//return menu_return::RETURN_EXIT_ALL;
|
|
}
|
|
} else if (actionKey == "Exit")
|
|
{
|
|
return menu_return::RETURN_EXIT_ALL;
|
|
}
|
|
|
|
ShowMenu();
|
|
return menu_return::RETURN_REPAINT;
|
|
}
|
|
|
|
bool CRecordManager::ShowMenu(void)
|
|
{
|
|
int select = -1, rec_count = recmap.size();
|
|
char cnt[5];
|
|
CMenuForwarder * item;
|
|
CMenuForwarder * iteml;
|
|
t_channel_id channel_ids[RECORD_MAX_COUNT] = { 0 }; /* initialization avoids false "might */
|
|
int recording_ids[RECORD_MAX_COUNT] = { 0 }; /* be used uninitialized" warning */
|
|
durations.clear();
|
|
|
|
CMenuSelectorTarget * selector = new CMenuSelectorTarget(&select);
|
|
|
|
menu = new CMenuWidget(LOCALE_MAINMENU_RECORDING, NEUTRINO_ICON_RECORDING /*, width*/);
|
|
if (rec_count == 0)
|
|
menu->addKey(CRCInput::RC_stop, this, "Exit");
|
|
menu->addIntroItems(NONEXISTANT_LOCALE, NONEXISTANT_LOCALE, CMenuWidget::BTN_TYPE_CANCEL);
|
|
|
|
// Record / Timeshift
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
|
|
int rec_mode = GetRecordMode(live_channel_id);
|
|
bool status_ts = rec_mode & RECMODE_TSHIFT;
|
|
//bool status_rec = rec_mode & RECMODE_REC;
|
|
|
|
//record item
|
|
iteml = new CMenuForwarder(LOCALE_RECORDINGMENU_MULTIMENU_REC_AKT, true /*!status_rec*/, NULL, this, "Record", CRCInput::RC_red);
|
|
//if no recordings are running, set the focus to the record menu item
|
|
menu->addItem(iteml, rec_count == 0 ? true: false);
|
|
|
|
if (!status_ts)
|
|
{
|
|
//timeshift item
|
|
iteml = new CMenuForwarder(LOCALE_RECORDINGMENU_MULTIMENU_TIMESHIFT, true /*!status_ts*/, NULL, this, "Timeshift", CRCInput::RC_yellow);
|
|
}
|
|
else
|
|
{
|
|
//keep timeshift record
|
|
iteml = new CMenuForwarder(LOCALE_RECORDINGMENU_MULTIMENU_TIMESHIFT2RECORD, true /*status_ts*/, NULL, this, "Timeshift2Record", CRCInput::RC_yellow);
|
|
for (recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++)
|
|
{
|
|
CRecordInstance *inst = it->second;
|
|
if (inst->Timeshift())
|
|
iteml->setInfoIconRight(inst->move_ts2rec ? NEUTRINO_ICON_MARKER_DIALOG_OK : NEUTRINO_ICON_MARKER_DIALOG_OFF);
|
|
}
|
|
}
|
|
menu->addItem(iteml, false);
|
|
|
|
if (rec_count > 0)
|
|
{
|
|
menu->addItem(new CMenuSeparator(CMenuSeparator::LINE | CMenuSeparator::STRING, LOCALE_MAINMENU_RECORDING_STOP));
|
|
mutex.lock();
|
|
|
|
int i = 0 , shortcut = 1;
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
CRecordInstance * inst = it->second;
|
|
|
|
channel_ids[i] = inst->GetChannelId();
|
|
recording_ids[i] = inst->GetRecordingId();
|
|
|
|
std::string title, duration;
|
|
inst->GetRecordString(title, duration);
|
|
durations.push_back(duration);
|
|
|
|
const char* mode_icon = NEUTRINO_ICON_MARKER_RECORD;
|
|
if (inst->Timeshift())
|
|
mode_icon = NEUTRINO_ICON_MARKER_TIMESHIFT;
|
|
|
|
sprintf(cnt, "%d", i);
|
|
//define stop key if only one record is running, otherwise define shortcuts
|
|
neutrino_msg_t rc_key = CRCInput::convertDigitToKey(shortcut++);
|
|
const char * btn_icon = NEUTRINO_ICON_BUTTON_OKAY;
|
|
if (rec_count == 1)
|
|
{
|
|
rc_key = CRCInput::RC_stop;
|
|
btn_icon = NEUTRINO_ICON_BUTTON_STOP;
|
|
}
|
|
item = new CMenuForwarder(title.c_str(), true, durations[i].c_str(), selector, cnt, rc_key, NULL, mode_icon);
|
|
item->setItemButton(btn_icon, true);
|
|
|
|
//if only one recording is running, set the focus to this menu item
|
|
menu->addItem(item, rec_count == 1 ? true: false);
|
|
i++;
|
|
if (i >= RECORD_MAX_COUNT)
|
|
break;
|
|
}
|
|
if (i > 1) //menu item "stopp all records"
|
|
{
|
|
menu->addItem(GenericMenuSeparatorLine);
|
|
iteml = new CMenuForwarder(LOCALE_RECORDINGMENU_MULTIMENU_STOP_ALL, true, NULL, this, "StopAll", CRCInput::RC_stop);
|
|
iteml->setItemButton(NEUTRINO_ICON_BUTTON_STOP, true);
|
|
|
|
//if more than one recording is running, set the focus to menu item 'stopp all records'
|
|
menu->addItem(iteml, rec_count > 1 ? true: false);
|
|
}
|
|
mutex.unlock();
|
|
}
|
|
|
|
menu->exec(NULL, "");
|
|
delete selector;
|
|
|
|
if (select >= 0 && select < RECORD_MAX_COUNT) {
|
|
/* in theory, timer event can expire while we in menu ? lock and check again */
|
|
mutex.lock();
|
|
CRecordInstance * inst = FindInstanceID(recording_ids[select]);
|
|
if(inst == NULL || recording_ids[select] != inst->GetRecordingId()) {
|
|
printf("%s: channel %" PRIx64 " event id %d not found\n", __func__, channel_ids[select], recording_ids[select]);
|
|
mutex.unlock();
|
|
return false;
|
|
}
|
|
mutex.unlock();
|
|
return AskToStop(channel_ids[select], recording_ids[select]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CRecordManager::AskToStop(const t_channel_id channel_id, const int recid)
|
|
{
|
|
std::string title, duration;
|
|
CRecordInstance * inst;
|
|
|
|
mutex.lock();
|
|
if (recid)
|
|
inst = FindInstanceID(recid);
|
|
else
|
|
inst = FindInstance(channel_id);
|
|
|
|
if(inst) {
|
|
inst->GetRecordString(title, duration);
|
|
title += " " + duration;
|
|
}
|
|
mutex.unlock();
|
|
if(inst == NULL)
|
|
return false;
|
|
|
|
if(ShowMsg(FindTimeshift() ? LOCALE_SHUTDOWN_TIMESHIFT_QUERY : LOCALE_SHUTDOWN_RECORDING_QUERY, title.c_str(),
|
|
CMsgBox::mbrYes, CMsgBox::mbYes | CMsgBox::mbNo, NULL, 450, 30) == CMsgBox::mbrYes) {
|
|
mutex.lock();
|
|
if (recid)
|
|
inst = FindInstanceID(recid);
|
|
else
|
|
inst = FindInstance(channel_id);
|
|
if (inst)
|
|
StopInstance(inst);
|
|
mutex.unlock();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CRecordManager::RunStartScript(void)
|
|
{
|
|
//FIXME only if no recordings yet or always ?
|
|
if(RecordingStatus())
|
|
return false;
|
|
|
|
//NI
|
|
printf("CRecordManager::%s: wakeup hdd...\n", __func__);
|
|
wakeup_hdd(g_settings.network_nfs_recordingdir.c_str(),true);
|
|
|
|
return exec_controlscript(NEUTRINO_RECORDING_START_SCRIPT);
|
|
}
|
|
|
|
bool CRecordManager::RunStopScript(void)
|
|
{
|
|
//FIXME only if no recordings left or always ?
|
|
if(RecordingStatus())
|
|
return false;
|
|
|
|
return exec_controlscript(NEUTRINO_RECORDING_ENDED_SCRIPT);
|
|
}
|
|
|
|
/*
|
|
* if we not recording and standby mode -> wakeup zapit
|
|
* check if we can record channel without zap
|
|
* if no - change mode and zap
|
|
* if zap fails - change mode back and return
|
|
* else if standby stop playback
|
|
* if yes
|
|
* zap_to_record
|
|
* if zap ok
|
|
* set record mode
|
|
*/
|
|
bool CRecordManager::CutBackNeutrino(const t_channel_id channel_id, CFrontend * &frontend)
|
|
{
|
|
bool ret = true;
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if (!channel)
|
|
return false;
|
|
|
|
int mode = channel->getServiceType() != ST_DIGITAL_RADIO_SOUND_SERVICE ?
|
|
NeutrinoModes::mode_tv : NeutrinoModes::mode_radio;
|
|
|
|
printf("%s channel_id %" PRIx64 " mode %d\n", __func__, channel_id, mode);
|
|
|
|
last_mode = CNeutrinoApp::getInstance()->getMode();
|
|
if(last_mode == NeutrinoModes::mode_standby && recmap.empty()) {
|
|
g_Zapit->setStandby(false); // this zap to live_channel_id
|
|
/* wait for zapit wakeup */
|
|
g_Zapit->getMode();
|
|
}
|
|
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
|
|
bool mode_changed = false;
|
|
CFrontend *live_fe = CZapit::getInstance()->GetLiveFrontend();
|
|
frontend = live_fe;
|
|
if(live_channel_id != channel_id) {
|
|
/* first try to get frontend for record with locked live */
|
|
bool unlock = true;
|
|
/* executed in neutrino thread - possible race with zap NOWAIT and epg scan zap */
|
|
CFEManager::getInstance()->Lock();
|
|
CFEManager::getInstance()->lockFrontend(live_fe);
|
|
frontend = CFEManager::getInstance()->allocateFE(channel, true);
|
|
if (frontend == NULL) {
|
|
/* no frontend, try again with unlocked live */
|
|
unlock = false;
|
|
CFEManager::getInstance()->unlockFrontend(live_fe);
|
|
frontend = CFEManager::getInstance()->allocateFE(channel, true);
|
|
}
|
|
CFEManager::getInstance()->Unlock();
|
|
|
|
if (frontend == NULL)
|
|
return false;
|
|
|
|
/* if allocateFE was successful, full zapTo_serviceID
|
|
* needed, if record frontend same as live, and its on different TP */
|
|
bool found = (live_fe != frontend) || IS_WEBCHAN(live_channel_id) || SAME_TRANSPONDER(live_channel_id, channel_id);
|
|
|
|
/* stop all streams on that fe, if we going to change transponder */
|
|
if (!frontend->sameTsidOnid(channel->getTransponderId()))
|
|
CStreamManager::getInstance()->StopStream(frontend);
|
|
|
|
if(found) {
|
|
ret = g_Zapit->zapTo_record(channel_id) > 0;
|
|
printf("%s found same tp, zapTo_record channel_id %" PRIx64 " result %d\n", __func__, channel_id, ret);
|
|
}
|
|
else {
|
|
printf("%s mode %d last_mode %d getLastMode %d\n", __FUNCTION__, mode, last_mode, CNeutrinoApp::getInstance()->getLastMode());
|
|
StopAutoRecord(false);
|
|
if (mode != last_mode && (last_mode != NeutrinoModes::mode_standby || mode != CNeutrinoApp::getInstance()->getLastMode())) {
|
|
CNeutrinoApp::getInstance()->handleMsg( NeutrinoMessages::CHANGEMODE , mode | NeutrinoModes::norezap );
|
|
mode_changed = true;
|
|
}
|
|
|
|
ret = g_Zapit->zapTo_serviceID(channel_id) > 0;
|
|
printf("%s zapTo_serviceID channel_id %" PRIx64 " result %d\n", __func__, channel_id, ret);
|
|
}
|
|
if (unlock)
|
|
CFEManager::getInstance()->unlockFrontend(live_fe);
|
|
}
|
|
#ifdef DYNAMIC_DEMUX
|
|
else {
|
|
frontend = CFEManager::getInstance()->allocateFE(channel, true);
|
|
}
|
|
printf("%s: record demux: %d\n", __FUNCTION__, channel->getRecordDemux());
|
|
if (channel->getRecordDemux() == 0)
|
|
ret = false;
|
|
#endif
|
|
if(ret) {
|
|
#if ENABLE_PIP
|
|
/* FIXME until proper demux management */
|
|
t_channel_id pip_channel_id = CZapit::getInstance()->GetPipChannelID();
|
|
if ((pip_channel_id == channel_id) && (channel->getRecordDemux() == channel->getPipDemux()))
|
|
g_Zapit->stopPip();
|
|
#endif
|
|
|
|
if(StopSectionsd) {
|
|
printf("%s: g_Sectionsd->setPauseScanning(true)\n", __FUNCTION__);
|
|
g_Sectionsd->setPauseScanning(true);
|
|
}
|
|
|
|
/* after this zapit send EVT_RECORDMODE_ACTIVATED, so neutrino getting NeutrinoMessages::EVT_RECORDMODE */
|
|
g_Zapit->setRecordMode( true );
|
|
if(last_mode == NeutrinoModes::mode_standby)
|
|
g_Zapit->stopPlayBack();
|
|
if ((live_channel_id == channel_id) && g_Radiotext)
|
|
g_Radiotext->radiotext_stop();
|
|
/* in case channel_id == live_channel_id */
|
|
CStreamManager::getInstance()->StopStream(channel_id);
|
|
}
|
|
if(last_mode == NeutrinoModes::mode_standby) {
|
|
//CNeutrinoApp::getInstance()->handleMsg( NeutrinoMessages::CHANGEMODE , NeutrinoModes::mode_standby);
|
|
g_RCInput->postMsg( NeutrinoMessages::CHANGEMODE , last_mode);
|
|
} else if(!ret && mode_changed /*mode != last_mode*/)
|
|
CNeutrinoApp::getInstance()->handleMsg( NeutrinoMessages::CHANGEMODE , last_mode);
|
|
|
|
printf("%s channel_id %" PRIx64 " mode %d : result %s\n", __func__, channel_id, mode, ret ? "OK" : "BAD");
|
|
return ret;
|
|
}
|
|
|
|
void CRecordManager::RestoreNeutrino(void)
|
|
{
|
|
if(!recmap.empty())
|
|
return;
|
|
|
|
/* after this zapit send EVT_RECORDMODE_DEACTIVATED, so neutrino getting NeutrinoMessages::EVT_RECORDMODE */
|
|
g_Zapit->setRecordMode( false );
|
|
|
|
if((CNeutrinoApp::getInstance()->getMode() != NeutrinoModes::mode_standby) && StopSectionsd)
|
|
g_Sectionsd->setPauseScanning(false);
|
|
}
|
|
|
|
CRecordInstance* CRecordManager::getRecordInstance(std::string file)
|
|
{
|
|
mutex.lock();
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
CRecordInstance * inst = it->second;
|
|
if ((((std::string)inst->GetFileName()) + ".ts") == file) {
|
|
mutex.unlock();
|
|
return inst;
|
|
}
|
|
}
|
|
mutex.unlock();
|
|
return NULL;
|
|
}
|
|
|
|
//NI
|
|
CRecordInstance* CRecordManager::getUseCI()
|
|
{
|
|
mutex.lock();
|
|
for(recmap_iterator_t it = recmap.begin(); it != recmap.end(); it++) {
|
|
CRecordInstance * inst = it->second;
|
|
if (inst)
|
|
{
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(inst->GetChannelId());
|
|
if (channel && channel->bUseCI)
|
|
{
|
|
mutex.unlock();
|
|
return inst;
|
|
}
|
|
}
|
|
}
|
|
mutex.unlock();
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
/* should return true, if recordingstatus changed in this function ? */
|
|
bool CRecordManager::doGuiRecord()
|
|
{
|
|
bool refreshGui = false;
|
|
std::string recDir;
|
|
|
|
t_channel_id live_channel_id = CZapit::getInstance()->GetCurrentChannelID();
|
|
if(recordingstatus == 1) {
|
|
bool doRecord = true;
|
|
#if 0 //FIXME unused ?
|
|
doRecord = CRecordManager::getInstance()->ChooseRecDir(recDir);
|
|
#endif
|
|
printf("%s: start to dir %s\n", __FUNCTION__, recDir.c_str());
|
|
if(!doRecord || (Record(live_channel_id, recDir.c_str()) == false))
|
|
{
|
|
recordingstatus=0;
|
|
refreshGui = true;
|
|
}
|
|
} else {
|
|
int recording_id = 0;
|
|
mutex.lock();
|
|
CRecordInstance * inst = FindInstance(live_channel_id);
|
|
if(inst)
|
|
recording_id = inst->GetRecordingId();
|
|
mutex.unlock();
|
|
if(recording_id)
|
|
g_Timerd->stopTimerEvent(recording_id);
|
|
}
|
|
return refreshGui;
|
|
}
|
|
|
|
bool CRecordManager::changeNotify(const neutrino_locale_t OptionName, void * /*data*/)
|
|
{
|
|
bool ret = false;
|
|
if ((ARE_LOCALES_EQUAL(OptionName, LOCALE_MAINMENU_RECORDING_START)) || (ARE_LOCALES_EQUAL(OptionName, LOCALE_MAINMENU_RECORDING)))
|
|
{
|
|
/* called after option (recordingstatus) changed and painted
|
|
* recordingstatus = 1 -> start live channe, 0 -> stop live channel record */
|
|
if(g_RemoteControl->is_video_started) {
|
|
ret = doGuiRecord();
|
|
}
|
|
else {
|
|
if(recordingstatus)
|
|
ret = true;
|
|
recordingstatus = 0;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
#endif
|
|
#if 0
|
|
/* this is saved copy of neutrino code which seems was not used for some time */
|
|
bool CRecordManager::ChooseRecDir(std::string &dir)
|
|
{
|
|
bool doRecord = true;
|
|
|
|
if(g_settings.recording_choose_direct_rec_dir == 2) {
|
|
CFileBrowser b;
|
|
b.Dir_Mode=true;
|
|
if (b.exec(g_settings.network_nfs_recordingdir)) {
|
|
dir = b.getSelectedFile()->Name;
|
|
}
|
|
else doRecord = false;
|
|
}
|
|
else if(g_settings.recording_choose_direct_rec_dir == 1) {
|
|
int userDecision = -1;
|
|
CMountChooser recDirs(LOCALE_TIMERLIST_RECORDING_DIR,NEUTRINO_ICON_SETTINGS,&userDecision,NULL,g_settings.network_nfs_recordingdir);
|
|
doRecord = false;
|
|
if (recDirs.hasItem()) {
|
|
recDirs.exec(NULL,"");
|
|
if (userDecision != -1) {
|
|
dir = g_settings.network_nfs_local_dir[userDecision];
|
|
doRecord = MountDirectory(dir.c_str());
|
|
}
|
|
} else
|
|
printf("%s: no network devices available\n", __FUNCTION__);
|
|
}
|
|
return doRecord;
|
|
}
|
|
|
|
bool CRecordManager::MountDirectory(const char *recordingDir)
|
|
{
|
|
bool ret = true;
|
|
|
|
if (!CFSMounter::isMounted(recordingDir)) {
|
|
for(int i=0 ; i < NETWORK_NFS_NR_OF_ENTRIES ; i++) {
|
|
if (strcmp(g_settings.network_nfs_local_dir[i],recordingDir) == 0) {
|
|
CFSMounter::MountRes mres =
|
|
CFSMounter::mount(g_settings.network_nfs_ip[i].c_str(),
|
|
g_settings.network_nfs_dir[i],
|
|
g_settings.network_nfs_local_dir[i],
|
|
(CFSMounter::FSType) g_settings.network_nfs_type[i],
|
|
g_settings.network_nfs_username[i],
|
|
g_settings.network_nfs_password[i],
|
|
g_settings.network_nfs_mount_options1[i],
|
|
g_settings.network_nfs_mount_options2[i]);
|
|
if (mres != CFSMounter::MRES_OK) {
|
|
const char * merr = mntRes2Str(mres);
|
|
int msglen = strlen(merr) + strlen(recordingDir) + 7;
|
|
char msg[msglen];
|
|
strcpy(msg,merr);
|
|
strcat(msg,"\nDir: ");
|
|
strcat(msg,recordingDir);
|
|
|
|
ShowMsg(LOCALE_MESSAGEBOX_ERROR, msg,
|
|
CMsgBox::mbrBack, CMsgBox::mbBack,NEUTRINO_ICON_ERROR, 450, 10); // UTF-8
|
|
ret = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if 0 // not used, saved in case we needed it
|
|
extern bool autoshift_delete;
|
|
bool CRecordManager::LinkTimeshift()
|
|
{
|
|
if(autoshift) {
|
|
char buf[512];
|
|
autoshift = false;
|
|
sprintf(buf, "ln %s/* %s", timeshiftDir, g_settings.network_nfs_recordingdir);
|
|
my_system(3, "/bin/sh", "-c", buf); //NI
|
|
autoshift_delete = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CStreamRec::CStreamRec(const CTimerd::RecordingInfo * const eventinfo, std::string &dir, bool timeshift, bool stream_vtxt_pid, bool stream_pmt_pid, bool stream_subtitle_pids)
|
|
: CRecordInstance(eventinfo, dir, timeshift, stream_vtxt_pid, stream_pmt_pid, stream_subtitle_pids)
|
|
{
|
|
ifcx = NULL;
|
|
ofcx = NULL;
|
|
stopped = true;
|
|
interrupt = false;
|
|
bsfc = NULL;
|
|
}
|
|
|
|
CStreamRec::~CStreamRec()
|
|
{
|
|
Stop();
|
|
Close();
|
|
}
|
|
|
|
void CStreamRec::Close()
|
|
{
|
|
if (ifcx) {
|
|
avformat_close_input(&ifcx);
|
|
}
|
|
if (ofcx) {
|
|
if (ofcx->pb) {
|
|
avio_close(ofcx->pb);
|
|
ofcx->pb = NULL;
|
|
}
|
|
avformat_free_context(ofcx);
|
|
}
|
|
if (bsfc){
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 100)
|
|
av_bitstream_filter_close(bsfc);
|
|
#else
|
|
av_bsf_free(&bsfc);
|
|
#endif
|
|
}
|
|
ifcx = NULL;
|
|
ofcx = NULL;
|
|
bsfc = NULL;
|
|
}
|
|
|
|
void CStreamRec::GetPids(CZapitChannel * channel)
|
|
{
|
|
channel = channel;
|
|
}
|
|
|
|
void CStreamRec::FillMovieInfo(CZapitChannel * /*channel*/, APIDList & /*apid_list*/)
|
|
{
|
|
recMovieInfo->VideoType = 0;
|
|
|
|
for (unsigned i = 0; i < ofcx->nb_streams; i++) {
|
|
AVStream *st = ofcx->streams[i];
|
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 25, 101)
|
|
AVCodecContext * codec = st->codec;
|
|
#else
|
|
AVCodecParameters * codec = st->codecpar;
|
|
#endif
|
|
if (codec->codec_type == AVMEDIA_TYPE_AUDIO) {
|
|
AUDIO_PIDS audio_pids;
|
|
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
|
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
|
|
|
|
std::string desc;
|
|
if (lang)
|
|
desc += lang->value;
|
|
|
|
if (title) {
|
|
if (desc.length() != 0)
|
|
desc += " ";
|
|
desc += title->value;
|
|
}
|
|
switch(codec->codec_id) {
|
|
case AV_CODEC_ID_AC3:
|
|
audio_pids.atype = CZapitAudioChannel::AC3;
|
|
break;
|
|
case AV_CODEC_ID_AAC:
|
|
audio_pids.atype = CZapitAudioChannel::AAC;
|
|
break;
|
|
case AV_CODEC_ID_EAC3:
|
|
audio_pids.atype = CZapitAudioChannel::EAC3;
|
|
break;
|
|
case AV_CODEC_ID_MP2:
|
|
default:
|
|
audio_pids.atype = CZapitAudioChannel::MPEG;
|
|
break;
|
|
}
|
|
|
|
audio_pids.selected = 0;
|
|
audio_pids.AudioPidName = desc;
|
|
audio_pids.AudioPid = st->id;
|
|
recMovieInfo->audioPids.push_back(audio_pids);
|
|
printf("%s: [AUDIO] 0x%x [%s]\n", __FUNCTION__, audio_pids.AudioPid, desc.c_str());
|
|
|
|
} else if (codec->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
recMovieInfo->VideoPid = st->id;
|
|
if (codec->codec_id == AV_CODEC_ID_H264)
|
|
recMovieInfo->VideoType = 1;
|
|
printf("%s: [VIDEO] 0x%x\n", __FUNCTION__, recMovieInfo->VideoPid);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CStreamRec::Start()
|
|
{
|
|
if (!stopped)
|
|
return false;
|
|
stopped = false;
|
|
int ret = start();
|
|
return (ret == 0);
|
|
}
|
|
|
|
bool CStreamRec::Stop(bool remove_event)
|
|
{
|
|
if (stopped)
|
|
return false;
|
|
|
|
time_t end_time = time_monotonic();
|
|
CHintBox hintBox(LOCALE_MESSAGEBOX_INFO, rec_stop_msg.c_str());
|
|
if ((!(autoshift && g_settings.timeshift_auto)) && g_settings.recording_startstop_msg)
|
|
hintBox.paint();
|
|
|
|
printf("%s: Stopping...\n", __FUNCTION__);
|
|
interrupt = true;
|
|
stopped = true;
|
|
int ret = join();
|
|
interrupt = false;
|
|
|
|
if(recording_id && remove_event) {
|
|
g_Timerd->stopTimerEvent(recording_id);
|
|
recording_id = 0;
|
|
}
|
|
|
|
struct stat test;
|
|
std::string xmlfile = std::string(filename) + ".xml";
|
|
if(stat(xmlfile.c_str(), &test) == 0){
|
|
recMovieInfo->clear();
|
|
std::string tsfile = std::string(filename) + ".ts";
|
|
recMovieInfo->file.Name = tsfile;
|
|
cMovieInfo->loadMovieInfo(recMovieInfo);//restore user bookmark
|
|
}
|
|
|
|
recMovieInfo->length = (int) round((double) (end_time - time_started) / (double) 60);
|
|
printf("%s: len %d\n", __FUNCTION__, recMovieInfo->length);
|
|
|
|
SaveXml();
|
|
hintBox.hide();
|
|
return (ret == 0);
|
|
}
|
|
|
|
record_error_msg_t CStreamRec::Record()
|
|
{
|
|
APIDList apid_list;
|
|
|
|
CHintBox hintBox(LOCALE_MESSAGEBOX_INFO, g_Locale->getText(LOCALE_RECORDING_START));
|
|
if ((!(autoshift && g_settings.timeshift_auto)) && g_settings.recording_startstop_msg)
|
|
hintBox.paint();
|
|
|
|
printf("%s: channel %" PRIx64 " recording_id %d\n", __func__, channel_id, recording_id);
|
|
CZapitChannel * channel = CServiceManager::getInstance()->FindChannel(channel_id);
|
|
if(channel == NULL) {
|
|
printf("%s: channel %" PRIx64 " not found!\n", __func__, channel_id);
|
|
hintBox.hide();
|
|
return RECORD_INVALID_CHANNEL;
|
|
}
|
|
|
|
record_error_msg_t ret = MakeFileName(channel);
|
|
if(ret != RECORD_OK) {
|
|
hintBox.hide();
|
|
return ret;
|
|
}
|
|
|
|
CRecordInstance::FillMovieInfo(channel, apid_list);
|
|
if (!Open(channel) || !Start()) {
|
|
Close();
|
|
hintBox.hide();
|
|
return RECORD_FAILURE;
|
|
}
|
|
FillMovieInfo(channel, apid_list);
|
|
|
|
SaveXml();
|
|
if(recording_id == 0) {
|
|
time_t now = time(NULL);
|
|
int record_end;
|
|
if (autoshift) {
|
|
record_end = now+g_settings.timeshift_hours*60*60;
|
|
} else {
|
|
record_end = now+g_settings.record_hours*60*60;
|
|
if (g_settings.recording_epg_for_end) {
|
|
int pre=0, post=0;
|
|
CEPGData epgData;
|
|
if (CEitManager::getInstance()->getActualEPGServiceKey(channel->getEpgID(), &epgData )) {
|
|
g_Timerd->getRecordingSafety(pre, post);
|
|
if (epgData.epg_times.startzeit > 0)
|
|
record_end = epgData.epg_times.startzeit + epgData.epg_times.dauer + post;
|
|
}
|
|
}
|
|
}
|
|
recording_id = g_Timerd->addImmediateRecordTimerEvent(channel_id, now, record_end, epg_id, epg_time, apidmode);
|
|
printf("%s: channel %" PRIx64 " -> timer eventID %d\n", __func__, channel_id, recording_id);
|
|
}
|
|
hintBox.hide();
|
|
|
|
return RECORD_OK;
|
|
}
|
|
|
|
int CStreamRec::Interrupt(void * data)
|
|
{
|
|
CStreamRec * sr = (CStreamRec*) data;
|
|
if (sr->interrupt)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
bool CStreamRec::Open(CZapitChannel * channel)
|
|
{
|
|
std::string url = channel->getUrl();
|
|
|
|
if (url.empty())
|
|
return false;
|
|
|
|
std::string pretty_name,headers,dumb;
|
|
if (!CMoviePlayerGui::getInstance(true).getLiveUrl(channel->getUrl(), channel->getScriptName(), url, pretty_name, recMovieInfo->epgInfo1, recMovieInfo->epgInfo2,headers,dumb)) {
|
|
printf("%s: getLiveUrl() [%s] failed!\n", __FUNCTION__, url.c_str());
|
|
return false;
|
|
}
|
|
|
|
//av_log_set_level(AV_LOG_VERBOSE);
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
|
|
av_register_all();
|
|
avcodec_register_all();
|
|
#endif
|
|
avformat_network_init();
|
|
printf("%s: Open input [%s]....\n", __FUNCTION__, url.c_str());
|
|
|
|
AVDictionary *options = NULL;
|
|
if (!headers.empty())//add cookies
|
|
av_dict_set(&options, "headers", headers.c_str(), 0);
|
|
|
|
if (avformat_open_input(&ifcx, url.c_str(), NULL, &options) != 0) {
|
|
printf("%s: Cannot open input [%s]!\n", __FUNCTION__, url.c_str());
|
|
if (!headers.empty())
|
|
av_dict_free(&options);
|
|
return false;
|
|
}
|
|
if (!headers.empty())
|
|
av_dict_free(&options);
|
|
|
|
if (avformat_find_stream_info(ifcx, NULL) < 0) {
|
|
printf("%s: Cannot find stream info [%s]!\n", __FUNCTION__, channel->getUrl().c_str());
|
|
return false;
|
|
}
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 27, 102)
|
|
const char *hls = "applehttp";
|
|
#else
|
|
const char *hls = "hls";
|
|
#endif
|
|
if (!strstr(ifcx->iformat->name, hls) &&
|
|
!strstr(ifcx->iformat->name, "mpegts") &&
|
|
!strstr(ifcx->iformat->name, "matroska") &&
|
|
!strstr(ifcx->iformat->name, "avi") &&
|
|
!strstr(ifcx->iformat->name, "mp4")) {
|
|
printf("%s: not supported format [%s]!\n", __FUNCTION__, ifcx->iformat->name);
|
|
return false;
|
|
}
|
|
|
|
AVIOInterruptCB int_cb = { Interrupt, this };
|
|
ifcx->interrupt_callback = int_cb;
|
|
|
|
#if (LIBAVFORMAT_VERSION_MAJOR < 58)
|
|
snprintf(ifcx->filename, sizeof(ifcx->filename), "%s", channel->getUrl().c_str());
|
|
av_dump_format(ifcx, 0, ifcx->filename, 0);
|
|
#else
|
|
av_dump_format(ifcx, 0, ifcx->url, 0);
|
|
#endif
|
|
|
|
std::string tsfile = std::string(filename) + ".ts";
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 0, 100)
|
|
AVOutputFormat *ofmt = av_guess_format(NULL, tsfile.c_str(), NULL);
|
|
#else
|
|
const AVOutputFormat *ofmt = av_guess_format(NULL, tsfile.c_str(), NULL);
|
|
#endif
|
|
if (ofmt == NULL) {
|
|
printf("%s: av_guess_format for [%s] failed!\n", __FUNCTION__, tsfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
ofcx = avformat_alloc_context();
|
|
ofcx->oformat = ofmt;
|
|
|
|
if (avio_open2(&ofcx->pb, tsfile.c_str(), AVIO_FLAG_WRITE, NULL, NULL) < 0) {
|
|
printf("%s: avio_open2 for [%s] failed!\n", __FUNCTION__, tsfile.c_str());
|
|
return false;
|
|
}
|
|
|
|
av_dict_copy(&ofcx->metadata, ifcx->metadata, 0);
|
|
#if (LIBAVFORMAT_VERSION_MAJOR < 58)
|
|
snprintf(ofcx->filename, sizeof(ofcx->filename), "%s", tsfile.c_str());
|
|
#else
|
|
ofcx->url = av_strdup(!tsfile.empty() ? tsfile.c_str() : "");
|
|
#endif
|
|
|
|
stream_index = -1;
|
|
int stid = 0x200;
|
|
for (unsigned i = 0; i < ifcx->nb_streams; i++) {
|
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 25, 101)
|
|
AVCodecContext * iccx = ifcx->streams[i]->codec;
|
|
AVStream *ost = avformat_new_stream(ofcx, iccx->codec);
|
|
avcodec_copy_context(ost->codec, iccx);
|
|
#else
|
|
AVCodecParameters * iccx = ifcx->streams[i]->codecpar;
|
|
AVStream *ost = avformat_new_stream(ofcx, NULL);
|
|
avcodec_parameters_copy(ost->codecpar, iccx);
|
|
#endif
|
|
av_dict_copy(&ost->metadata, ifcx->streams[i]->metadata, 0);
|
|
ost->time_base = ifcx->streams[i]->time_base;
|
|
ost->id = stid++;
|
|
if (iccx->codec_type == AVMEDIA_TYPE_VIDEO) {
|
|
stream_index = i;
|
|
} else if (stream_index < 0)
|
|
stream_index = i;
|
|
}
|
|
av_log_set_level(AV_LOG_VERBOSE);
|
|
#if (LIBAVFORMAT_VERSION_MAJOR < 58)
|
|
av_dump_format(ofcx, 0, ofcx->filename, 1);
|
|
#else
|
|
av_dump_format(ofcx, 0, ofcx->url, 1);
|
|
#endif
|
|
av_log_set_level(AV_LOG_WARNING);
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 100)
|
|
bsfc = av_bitstream_filter_init("h264_mp4toannexb");
|
|
if (!bsfc)
|
|
printf("%s: av_bitstream_filter_init h264_mp4toannexb failed!\n", __FUNCTION__);
|
|
#else
|
|
const AVBitStreamFilter *bsf = av_bsf_get_by_name("h264_mp4toannexb");
|
|
if(!bsf) {
|
|
return false;
|
|
}
|
|
if ((av_bsf_alloc(bsf, &bsfc))) {
|
|
return false;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void CStreamRec::run()
|
|
{
|
|
AVPacket pkt;
|
|
|
|
time_t now = 0;
|
|
time_t tstart = time_monotonic();
|
|
time_started = tstart;
|
|
start_time = time(0);
|
|
if (avformat_write_header(ofcx, NULL) < 0) {
|
|
printf("%s: avformat_write_header failed\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
|
|
double total = 0;
|
|
while (!stopped) {
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100)
|
|
av_init_packet(&pkt);
|
|
#endif
|
|
if (av_read_frame(ifcx, &pkt) < 0)
|
|
break;
|
|
if (pkt.stream_index < 0)
|
|
continue;
|
|
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(57, 25, 101)
|
|
AVCodecContext *codec = ifcx->streams[pkt.stream_index]->codec;
|
|
#else
|
|
AVCodecParameters *codec = ifcx->streams[pkt.stream_index]->codecpar;
|
|
#endif
|
|
if (bsfc && codec->codec_id == AV_CODEC_ID_H264) {
|
|
AVPacket newpkt = pkt;
|
|
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 100)
|
|
if (av_bitstream_filter_filter(bsfc, codec, NULL, &newpkt.data, &newpkt.size, pkt.data, pkt.size, pkt.flags & AV_PKT_FLAG_KEY) >= 0) {
|
|
av_packet_unref(&pkt);
|
|
newpkt.buf = av_buffer_create(newpkt.data, newpkt.size, av_buffer_default_free, NULL, 0);
|
|
pkt = newpkt;
|
|
}
|
|
#else
|
|
int ret = av_bsf_send_packet(bsfc, &pkt);
|
|
if (ret < 0){
|
|
break;
|
|
}
|
|
ret = av_bsf_receive_packet(bsfc, &newpkt);
|
|
if (ret == AVERROR(EAGAIN)){
|
|
break;
|
|
}
|
|
if(ret != AVERROR_EOF){
|
|
av_packet_unref(&pkt);
|
|
pkt = newpkt;
|
|
}
|
|
#endif
|
|
}
|
|
pkt.pts = av_rescale_q(pkt.pts, ifcx->streams[pkt.stream_index]->time_base, ofcx->streams[pkt.stream_index]->time_base);
|
|
pkt.dts = av_rescale_q(pkt.dts, ifcx->streams[pkt.stream_index]->time_base, ofcx->streams[pkt.stream_index]->time_base);
|
|
|
|
av_write_frame(ofcx, &pkt);
|
|
av_packet_unref(&pkt);
|
|
|
|
if (pkt.stream_index == stream_index) {
|
|
total += (double) 1000 * pkt.duration * av_q2d(ifcx->streams[stream_index]->time_base);
|
|
//printf("PKT: duration %d (%f) total %f (ifcx->duration %016llx\n", pkt.duration, duration, total, ifcx->duration);
|
|
}
|
|
|
|
if (now == 0)
|
|
WriteHeader(1000);
|
|
now = time_monotonic();
|
|
if (now - tstart > 1) {
|
|
tstart = now;
|
|
WriteHeader(total);
|
|
}
|
|
}
|
|
|
|
av_read_pause(ifcx);
|
|
av_write_trailer(ofcx);
|
|
WriteHeader(total);
|
|
printf("%s: Stopped.\n", __FUNCTION__);
|
|
}
|
|
|
|
typedef struct pvr_file_info
|
|
{
|
|
uint32_t uDuration; /* Time duration in Ms */
|
|
uint32_t uTSPacketSize;
|
|
} PVR_FILE_INFO;
|
|
|
|
void CStreamRec::WriteHeader(uint32_t duration)
|
|
{
|
|
std::string tsfile = std::string(filename) + ".ts";
|
|
//printf("%s: [%s] duration %d\n", __FUNCTION__, tsfile.c_str(), duration);
|
|
|
|
int srcfd = open(tsfile.c_str(), O_WRONLY | O_LARGEFILE);
|
|
if (srcfd >= 0) {
|
|
if (lseek64(srcfd, 188-sizeof(PVR_FILE_INFO), SEEK_SET) >= 0) {
|
|
PVR_FILE_INFO pinfo;
|
|
pinfo.uDuration = duration;
|
|
pinfo.uTSPacketSize = 188;
|
|
write(srcfd, (uint8_t *)&pinfo, sizeof(PVR_FILE_INFO));
|
|
}
|
|
close(srcfd);
|
|
} else
|
|
perror(tsfile.c_str());
|
|
}
|