diff options
Diffstat (limited to 'tderadio3/plugins/recording/recording.cpp')
-rw-r--r-- | tderadio3/plugins/recording/recording.cpp | 731 |
1 files changed, 731 insertions, 0 deletions
diff --git a/tderadio3/plugins/recording/recording.cpp b/tderadio3/plugins/recording/recording.cpp new file mode 100644 index 0000000..7b685f4 --- /dev/null +++ b/tderadio3/plugins/recording/recording.cpp @@ -0,0 +1,731 @@ +/*************************************************************************** + recording.cpp - description + ------------------- + begin : Mi Aug 27 2003 + copyright : (C) 2003 by Martin Witte + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "../../src/include/radiostation.h" +#include "../../src/include/errorlog-interfaces.h" +#include "../../src/include/aboutwidget.h" +#include "../../src/include/fileringbuffer.h" +#include "../../src/include/utils.h" + +#include "recording.h" +#include "recording-configuration.h" +#include "soundstreamevent.h" +#include "recording-monitor.h" +#include "encoder_mp3.h" +#include "encoder_ogg.h" +#include "encoder_pcm.h" + +#include <tqevent.h> +#include <tqapplication.h> +#include <tqregexp.h> + +#include <tdeconfig.h> +#include <tdeversion.h> + +#include <kaboutdata.h> + + +/////////////////////////////////////////////////////////////////////// +//// plugin library functions + +PLUGIN_LIBRARY_FUNCTIONS2( + Recording, "kradio-recording", i18n("TDERadio Recording Plugin"), + RecordingMonitor, i18n("TDERadio Recording Monitor") +); + +/////////////////////////////////////////////////////////////////////// + +Recording::Recording(const TQString &name) + : TQObject(NULL, NULL), + PluginBase(name, i18n("TDERadio Recording Plugin")), + m_config() +{ +} + + +Recording::~Recording() +{ + TQMapIterator<SoundStreamID, RecordingEncoding*> it = m_EncodingThreads.begin(); + TQMapIterator<SoundStreamID, RecordingEncoding*> end = m_EncodingThreads.end(); + for (; it != end; ++it) { + sendStopRecording(it.key()); + } +} + + +bool Recording::connectI(Interface *i) +{ + bool a = IRecCfg::connectI(i); + bool b = PluginBase::connectI(i); + bool c = ISoundStreamClient::connectI(i); + return a || b || c; +} + + +bool Recording::disconnectI(Interface *i) +{ + bool a = IRecCfg::disconnectI(i); + bool b = PluginBase::disconnectI(i); + bool c = ISoundStreamClient::disconnectI(i); + return a || b || c; +} + + +void Recording::noticeConnectedI (ISoundStreamServer *s, bool pointer_valid) +{ + ISoundStreamClient::noticeConnectedI(s, pointer_valid); + if (s && pointer_valid) { + s->register4_sendStartPlayback(this); + s->register4_sendStopPlayback(this); + s->register4_sendStartRecording(this); + s->register4_sendStartRecordingWithFormat(this); + s->register4_notifySoundStreamData(this); + s->register4_sendStopRecording(this); + s->register4_queryIsRecordingRunning(this); + s->register4_querySoundStreamDescription(this); + s->register4_querySoundStreamRadioStation(this); + s->register4_queryEnumerateSoundStreams(this); + s->register4_notifySoundStreamChanged(this); + s->register4_notifySoundStreamClosed(this); + } +} + +// PluginBase + +void Recording::saveState (TDEConfig *c) const +{ + c->setGroup(TQString("recording-") + PluginBase::name()); + m_config.saveConfig(c); +} + + +void Recording::restoreState (TDEConfig *c) +{ + c->setGroup(TQString("recording-") + PluginBase::name()); + RecordingConfig cfg; + cfg.restoreConfig(c); + setRecordingConfig(cfg); + //notifyRecordingConfigChanged(m_config); +} + + +ConfigPageInfo Recording::createConfigurationPage() +{ + RecordingConfiguration *c = new RecordingConfiguration(NULL); + connectI(c); + return ConfigPageInfo(c, + i18n("Recording"), + i18n("Recording"), + "kradio_record"); +} + + +AboutPageInfo Recording::createAboutPage() +{ +/* TDEAboutData aboutData("kradio", + NULL, + NULL, + I18N_NOOP("Recording Monitor for TDERadio"), + TDEAboutData::License_GPL, + "(c) 2002-2005 Martin Witte", + 0, + "http://sourceforge.net/projects/kradio", + 0); + aboutData.addAuthor("Martin Witte", "", "[email protected]"); + + return AboutPageInfo( + new TDERadioAboutWidget(aboutData, TDERadioAboutWidget::AbtTabbed), + i18n("Recording"), + i18n("Recording Plugin"), + "kradio_record" + );*/ + return AboutPageInfo(); +} + + +// IRecCfg + +bool Recording::setEncoderBuffer (size_t BufferSize, size_t BufferCount) +{ + if (m_config.m_EncodeBufferSize != BufferSize || + m_config.m_EncodeBufferCount != BufferCount) + { + m_config.m_EncodeBufferSize = BufferSize; + m_config.m_EncodeBufferCount = BufferCount; + notifyEncoderBufferChanged(BufferSize, BufferCount); + } + return true; +} + +bool Recording::setSoundFormat (const SoundFormat &sf) +{ + if (m_config.m_SoundFormat != sf) { + m_config.m_SoundFormat = sf; + notifySoundFormatChanged(sf); + } + return true; +} + +bool Recording::setMP3Quality (int q) +{ + if (m_config.m_mp3Quality != q) { + m_config.m_mp3Quality = q; + notifyMP3QualityChanged(q); + } + return true; +} + +bool Recording::setOggQuality (float q) +{ + if (m_config.m_oggQuality != q) { + m_config.m_oggQuality = q; + notifyOggQualityChanged(q); + } + return true; +} + +bool Recording::setRecordingDirectory(const TQString &dir) +{ + if (m_config.m_Directory != dir) { + m_config.m_Directory = dir; + notifyRecordingDirectoryChanged(dir); + } + return true; +} + +bool Recording::setOutputFormat (RecordingConfig::OutputFormat of) +{ + if (m_config.m_OutputFormat != of) { + m_config.m_OutputFormat = of; + notifyOutputFormatChanged(of); + } + return true; +} + +bool Recording::setPreRecording (bool enable, int seconds) +{ + if (m_config.m_PreRecordingEnable != enable || m_config.m_PreRecordingSeconds != seconds) { + m_config.m_PreRecordingEnable = enable; + m_config.m_PreRecordingSeconds = seconds; + + if (enable) { + for (TQMapIterator<SoundStreamID,FileRingBuffer*> it = m_PreRecordingBuffers.begin(); it != m_PreRecordingBuffers.end(); ++it) { + if (*it != NULL) { + delete *it; + } + *it = new FileRingBuffer(m_config.m_Directory + "/kradio-prerecord-"+TQString::number(it.key().getID()), m_config.m_PreRecordingSeconds * m_config.m_SoundFormat.m_SampleRate * m_config.m_SoundFormat.frameSize()); + SoundFormat sf = m_config.m_SoundFormat; + sendStartCaptureWithFormat(it.key(), sf, sf, false); + } + } + else { + for (TQMapIterator<SoundStreamID,FileRingBuffer*> it = m_PreRecordingBuffers.begin(); it != m_PreRecordingBuffers.end(); ++it) { + if (*it != NULL) { + sendStopCapture(it.key()); + delete *it; + } + } + m_PreRecordingBuffers.clear(); + } + + notifyPreRecordingChanged(enable, seconds); + } + return true; +} + +void Recording::getEncoderBuffer(size_t &BufferSize, size_t &BufferCount) const +{ + BufferSize = m_config.m_EncodeBufferSize; + BufferCount = m_config.m_EncodeBufferCount; +} + +const SoundFormat &Recording::getSoundFormat () const +{ + return m_config.m_SoundFormat; +} + +int Recording::getMP3Quality () const +{ + return m_config.m_mp3Quality; +} + +float Recording::getOggQuality () const +{ + return m_config.m_oggQuality; +} + +const TQString &Recording::getRecordingDirectory() const +{ + return m_config.m_Directory; +} + +RecordingConfig::OutputFormat Recording::getOutputFormat() const +{ + return m_config.m_OutputFormat; +} + +bool Recording::getPreRecording(int &seconds) const +{ + seconds = m_config.m_PreRecordingSeconds; + return m_config.m_PreRecordingEnable; +} + +const RecordingConfig &Recording::getRecordingConfig() const +{ + return m_config; +} + +bool Recording::setRecordingConfig(const RecordingConfig &c) +{ + setEncoderBuffer (c.m_EncodeBufferSize, c.m_EncodeBufferCount); + setSoundFormat (c.m_SoundFormat); + setMP3Quality (c.m_mp3Quality); + setOggQuality (c.m_oggQuality); + setRecordingDirectory(c.m_Directory); + setOutputFormat (c.m_OutputFormat); + setPreRecording (c.m_PreRecordingEnable, c.m_PreRecordingSeconds); + + m_config = c; + + notifyRecordingConfigChanged(m_config); + + return true; +} + + +// ISoundStreamClient +bool Recording::startPlayback(SoundStreamID id) +{ + if (m_PreRecordingBuffers.contains(id)) + delete m_PreRecordingBuffers[id]; + m_PreRecordingBuffers[id] = NULL; + if (m_config.m_PreRecordingEnable) { + m_PreRecordingBuffers[id] = new FileRingBuffer(m_config.m_Directory + "/kradio-prerecord-"+TQString::number(id.getID()), m_config.m_PreRecordingSeconds * m_config.m_SoundFormat.m_SampleRate * m_config.m_SoundFormat.frameSize()); + SoundFormat sf = m_config.m_SoundFormat; + sendStartCaptureWithFormat(id, sf, sf, false); + } + return false; +} + +bool Recording::stopPlayback(SoundStreamID id) +{ + if (m_PreRecordingBuffers.contains(id)) { + if (m_PreRecordingBuffers[id]) + delete m_PreRecordingBuffers[id]; + m_PreRecordingBuffers.remove(id); + sendStopCapture(id); + } + return false; +} + +bool Recording::startRecording(SoundStreamID id) +{ + +/* FileRingBuffer *test = new FileRingBuffer("/tmp/ringbuffertest", 2048); + char buffer1[1024]; + char buffer2[1024]; + char buffer3[1024]; + for (int i = 0; i < 1024; ++i) { + buffer1[i] = 'a'; + buffer2[i] = 'b'; + buffer3[i] = 'c'; + } + test->addData(buffer1, 1024); + test->addData(buffer2, 1024); + test->removeData(1024); + test->addData(buffer3, 1024); +*/ + + SoundFormat realFormat = m_config.m_SoundFormat; + return sendStartRecordingWithFormat(id, realFormat, realFormat); +} + +bool Recording::startRecordingWithFormat(SoundStreamID id, const SoundFormat &sf, SoundFormat &real_format) +{ + if (!sendStartCaptureWithFormat(id, sf, real_format, /* force_format = */ true)) { + logError(i18n("start capture not handled")); + return false; + } + + RecordingConfig cfg = m_config; + cfg.m_SoundFormat = real_format; + + logInfo(i18n("Recording starting")); + if (!startEncoder(id, cfg)) { + logError(i18n("starting encoding thread failed")); + sendStopCapture(id); + return false; + } + + return true; +} + + +bool Recording::stopRecording(SoundStreamID id) +{ + if (m_EncodingThreads.contains(id)) { + sendStopCapture(id); + if (m_config.m_PreRecordingEnable) { + if (!m_PreRecordingBuffers.contains(id)) { + if (m_PreRecordingBuffers[id] != NULL) { + delete m_PreRecordingBuffers[id]; + } + bool b = false; + queryIsPlaybackRunning(id, b); + if (b) { + m_PreRecordingBuffers[id] = new FileRingBuffer(m_config.m_Directory + "/kradio-prerecord-"+TQString::number(id.getID()), m_config.m_PreRecordingSeconds * m_config.m_SoundFormat.m_SampleRate * m_config.m_SoundFormat.frameSize()); + } else { + m_PreRecordingBuffers[id] = NULL; + } + } + } + stopEncoder(id); + return true; + } + return false; +} + + + +bool Recording::noticeSoundStreamData(SoundStreamID id, + const SoundFormat &/*sf*/, const char *data, size_t size, size_t &consumed_size, + const SoundMetaData &md +) +{ + if (m_PreRecordingBuffers.contains(id) && m_PreRecordingBuffers[id] != NULL) { + + FileRingBuffer &fbuf = *m_PreRecordingBuffers[id]; + if (fbuf.getFreeSize() < size) { + fbuf.removeData(size - fbuf.getFreeSize()); + } + size_t n = fbuf.addData(data, size); + consumed_size = (consumed_size == SIZE_T_DONT_CARE) ? n : min(consumed_size, n); +// if (n != size) { +// logDebug("recording packet: was not written completely to tmp buf"); +// } + +// //BEGIN DEBUG +// char tmp[4096]; +// for (unsigned int i = 0; i < sizeof(tmp); ++i) { tmp[i] = 0; } +// if (fbuf.getFreeSize() < sizeof(tmp)) { +// fbuf.removeData(sizeof(tmp) - fbuf.getFreeSize()); +// } +// fbuf.addData((char*)tmp, sizeof(tmp)); +// //END DEBUG + + if (m_EncodingThreads.contains(id)) { + + //logDebug("recording packet: " + TQString::number(size)); + + RecordingEncoding *thread = m_EncodingThreads[id]; + + //logDebug("noticeSoundStreamData thread = " + TQString::number((long long)thread, 16)); + + size_t remSize = fbuf.getFillSize(); + + while (remSize > 0) { + size_t bufferSize = remSize; + char *buf = thread->lockInputBuffer(bufferSize); + if (!buf) { + // Encoder buffer is full and bigger than remaining data + break; + } + if (bufferSize > remSize) { + bufferSize = remSize; + } + if (fbuf.takeData(buf, bufferSize) != bufferSize) { + logError(i18n("could not read suffient data")); + } + + thread->unlockInputBuffer(bufferSize, md); + remSize -= bufferSize; + } + + if (remSize == 0) { + delete m_PreRecordingBuffers[id]; + m_PreRecordingBuffers.remove(id); + } + } + + return true; + } + + else if (m_EncodingThreads.contains(id)) { + + //logDebug("recording packet: " + TQString::number(size)); + + RecordingEncoding *thread = m_EncodingThreads[id]; + + //logDebug("noticeSoundStreamData thread = " + TQString::number((long long)thread, 16)); + + size_t remSize = size; + const char *remData = data; + + while (remSize > 0) { + size_t bufferSize = remSize; + char *buf = thread->lockInputBuffer(bufferSize); + if (!buf) { + logWarning(i18n("Encoder input buffer overflow (buffer configuration problem?). Skipped %1 input bytes").arg(TQString::number(remSize))); + break; + } + if (bufferSize > remSize) { + bufferSize = remSize; + } + memcpy(buf, remData, bufferSize); + + thread->unlockInputBuffer(bufferSize, md); + remSize -= bufferSize; + remData += bufferSize; + } + consumed_size = (consumed_size == SIZE_T_DONT_CARE) ? size - remSize : min(consumed_size, size - remSize); + + return true; + } + return false; +} + + + + +bool Recording::startEncoder(SoundStreamID ssid, const RecordingConfig &cfg) +{ + if (m_EncodingThreads.contains(ssid)) + return false; + + SoundStreamID encID = createNewSoundStream(ssid, false); + m_RawStreams2EncodedStreams[ssid] = encID; + m_EncodedStreams2RawStreams[encID] = ssid; + + TQString ext = ".wav"; + switch (m_config.m_OutputFormat) { + case RecordingConfig::outputWAV: ext = ".wav"; break; + case RecordingConfig::outputAIFF: ext = ".aiff"; break; + case RecordingConfig::outputAU: ext = ".au"; break; +#ifdef HAVE_LAME + case RecordingConfig::outputMP3: ext = ".mp3"; break; +#endif +#ifdef HAVE_LAME + case RecordingConfig::outputOGG: ext = ".ogg"; break; +#endif + case RecordingConfig::outputRAW: ext = ".raw"; break; + default: ext = ".wav"; break; + } + const RadioStation *rs = NULL; + querySoundStreamRadioStation(ssid, rs); + TQString station = rs ? rs->name() + "-" : ""; + station.replace(TQRegExp("[/*?]"), "_"); + + TQDate date = TQDate::currentDate(); + TQTime time = TQTime::currentTime(); + TQString sdate; + + sdate.sprintf("%d.%d.%d.%d.%d",date.year(),date.month(),date.day(),time.hour(),time.minute()); + + TQString output = m_config.m_Directory + + "/kradio-recording-" + + station + + sdate + + ext; + + logInfo(i18n("Recording::outputFile: ") + output); + + RecordingEncoding *thread = NULL; + switch (m_config.m_OutputFormat) { +#ifdef HAVE_LAME + case RecordingConfig::outputMP3: + thread = new RecordingEncodingMP3(this, ssid, cfg, rs, output); + break; +#endif +#ifdef HAVE_OGG + case RecordingConfig::outputOGG: + thread = new RecordingEncodingOgg(this, ssid, cfg, rs, output); + break; +#endif + default: + thread = new RecordingEncodingPCM(this, ssid, cfg, rs, output); + } + + //m_encodingThread->openOutput(output, rs); + + if (thread->error()) { + //m_context.setError(); + logError(thread->errorString()); + } else { + thread->start(); + } + // store thread even if it has indicated an error + m_EncodingThreads[ssid] = thread; + + //logDebug("startEncoder thread = " + TQString::number((long long)thread, 16)); + + notifySoundStreamCreated(encID); + return !thread->error(); +} + + +void Recording::stopEncoder(SoundStreamID id) +{ + if (m_EncodingThreads.contains(id)) { + + RecordingEncoding *thread = m_EncodingThreads[id]; + + thread->setDone(); + + //logDebug("stopEncoder thread = " + TQString::number((long long)thread, 16)); + //logDebug("stopEncoder thread error = " + TQString::number(thread->error(), 16)); + + // FIXME: set a timer and do waiting "in background" + if (!thread->wait(5000)) { + //m_context.setError(); + logError(i18n("The encoding thread did not finish. It will be killed now.")); + thread->terminate(); + thread->wait(); + } else { + if (thread->error()) { + //m_context.setError(); + logError(thread->errorString()); + } else { + //TQ_UINT64 size = thread->encodedSize(); + //m_context.setEncodedSize(low, high); + //notifyRecordingContextChanged(m_context); + } + } + delete thread; + m_EncodingThreads.remove(id); + SoundStreamID encID = m_RawStreams2EncodedStreams[id]; + m_EncodedStreams2RawStreams.remove(encID); + m_RawStreams2EncodedStreams.remove(id); + sendStopPlayback(encID); + closeSoundStream(encID); + logInfo(i18n("Recording stopped")); + } +} + + +bool Recording::event(TQEvent *_e) +{ + if (SoundStreamEvent::isSoundStreamEvent(_e)) { + SoundStreamEvent *e = static_cast<SoundStreamEvent*>(_e); + SoundStreamID id = e->getSoundStreamID(); + + if (m_EncodingThreads.contains(id)) { + + RecordingEncoding *thread = m_EncodingThreads[id]; + + //logDebug("Recording::event: thread = " + TQString::number((long long)thread, 16)); + + if (thread->error()) { + logError(thread->errorString()); + //m_context.setError(); + stopEncoder(id); + } else { + //TQ_UINT64 size = thread->encodedSize(); + //m_context.setEncodedSize(low, high); + //notifyRecordingContextChanged(m_context); + if (e->type() == EncodingTerminated) { + stopEncoder(id); + } else if (e->type() == EncodingStep) { + SoundStreamEncodingStepEvent *step = static_cast<SoundStreamEncodingStepEvent*>(e); + size_t consumed_size = SIZE_T_DONT_CARE; + notifySoundStreamData(m_RawStreams2EncodedStreams[id], thread->config().m_SoundFormat, + step->data(), step->size(), consumed_size, step->metaData()); + if (consumed_size != SIZE_T_DONT_CARE && consumed_size < step->size()) { + logError(i18n("Recording::notifySoundStreamData(encoded data): Receivers skipped %1 Bytes").arg(step->size() - consumed_size)); + } + } + } + } + return true; + } else { + return TQObject::event(_e); + } +} + + +bool Recording::getSoundStreamDescription(SoundStreamID id, TQString &descr) const +{ + if (m_EncodedStreams2RawStreams.contains(id)) { + if (querySoundStreamDescription(m_EncodedStreams2RawStreams[id], descr)) { + descr = name() + " - " + descr; + return true; + } + } + return false; +} + + +bool Recording::getSoundStreamRadioStation(SoundStreamID id, const RadioStation *&rs) const +{ + if (m_EncodedStreams2RawStreams.contains(id)) { + if (querySoundStreamRadioStation(m_EncodedStreams2RawStreams[id], rs)) { + return true; + } + } + return false; +} + + +bool Recording::enumerateSoundStreams(TQMap<TQString, SoundStreamID> &list) const +{ + TQMapConstIterator<SoundStreamID,SoundStreamID> end = m_RawStreams2EncodedStreams.end(); + for (TQMapConstIterator<SoundStreamID,SoundStreamID> it = m_RawStreams2EncodedStreams.begin(); it != end; ++it) { + TQString tmp = TQString(); + getSoundStreamDescription(*it, tmp); + list[tmp] = *it; + } + return m_RawStreams2EncodedStreams.count() > 0; +} + + +bool Recording::noticeSoundStreamChanged(SoundStreamID id) +{ + if (m_RawStreams2EncodedStreams.contains(id)) { + notifySoundStreamChanged(m_RawStreams2EncodedStreams[id]); + return true; + } + return false; +} + + +bool Recording::isRecordingRunning(SoundStreamID id, bool &b, SoundFormat &sf) const +{ + if (m_EncodingThreads.contains(id)) { + b = m_EncodingThreads[id]->running(); + sf = getSoundFormat(); + return true; + } + return false; +} + + +bool Recording::noticeSoundStreamClosed(SoundStreamID id) +{ + if (m_PreRecordingBuffers.contains(id)) { + if (m_PreRecordingBuffers[id]) + delete m_PreRecordingBuffers[id]; + m_PreRecordingBuffers.remove(id); + } + + if (m_EncodingThreads.contains(id)) { + sendStopRecording(id); + return true; + } + return false; +} + + +#include "recording.moc" |