You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tderadio/kradio3/plugins/recording/recording.cpp

732 lines
22 KiB

/***************************************************************************
recording.cpp - description
-------------------
begin : Mi Aug 27 2003
copyright : (C) 2003 by Martin Witte
email : witte@kawo1.rwth-aachen.de
***************************************************************************/
/***************************************************************************
* *
* 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 <kconfig.h>
#include <tdeversion.h>
#include <kaboutdata.h>
///////////////////////////////////////////////////////////////////////
//// plugin library functions
PLUGIN_LIBRARY_FUNCTIONS2(
Recording, "kradio-recording", i18n("KRadio Recording Plugin"),
RecordingMonitor, i18n("KRadio Recording Monitor")
);
///////////////////////////////////////////////////////////////////////
Recording::Recording(const TQString &name)
: TQObject(NULL, NULL),
PluginBase(name, i18n("KRadio 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 KRadio"),
TDEAboutData::License_GPL,
"(c) 2002-2005 Martin Witte",
0,
"http://sourceforge.net/projects/kradio",
0);
aboutData.addAuthor("Martin Witte", "", "witte@kawo1.rwth-aachen.de");
return AboutPageInfo(
new KRadioAboutWidget(aboutData, KRadioAboutWidget::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"