|
|
|
/***************************************************************************
|
|
|
|
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 <kdeversion.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 (KConfig *c) const
|
|
|
|
{
|
|
|
|
c->setGroup(TQString("recording-") + PluginBase::name());
|
|
|
|
m_config.saveConfig(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Recording::restoreState (KConfig *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()
|
|
|
|
{
|
|
|
|
/* KAboutData aboutData("kradio",
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
I18N_NOOP("Recording Monitor for KRadio"),
|
|
|
|
KAboutData::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"
|