diff --git a/icons/icons.qrc b/icons/icons.qrc index a65c87c34..07232d1b6 100644 --- a/icons/icons.qrc +++ b/icons/icons.qrc @@ -43,5 +43,7 @@ clock.png sequence.png folder.png + record.png + record-disabled.png diff --git a/icons/record-disabled.png b/icons/record-disabled.png new file mode 100644 index 000000000..9510d9c8c Binary files /dev/null and b/icons/record-disabled.png differ diff --git a/icons/record.png b/icons/record.png new file mode 100644 index 000000000..87e09bfd8 Binary files /dev/null and b/icons/record.png differ diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 8448528b2..b2ef9e3e1 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -193,6 +193,7 @@ void Timeline::update_sequence() { ui->snappingButton->setEnabled(!null_sequence); ui->pushButton_4->setEnabled(!null_sequence); ui->pushButton_5->setEnabled(!null_sequence); + ui->recordButton->setEnabled(!null_sequence); ui->addButton->setEnabled(!null_sequence); ui->headers->setEnabled(!null_sequence); @@ -1152,3 +1153,7 @@ void Timeline::setScroll(int s) { ui->headers->set_scroll(s); repaint_timeline(); } + +void Timeline::on_recordButton_clicked() { + qDebug() << "recording is a" << start_recording(); +} diff --git a/panels/timeline.h b/panels/timeline.h index c92db4b62..cad39b7af 100644 --- a/panels/timeline.h +++ b/panels/timeline.h @@ -194,6 +194,8 @@ private slots: void setScroll(int); + void on_recordButton_clicked(); + private: QVector tool_buttons; void decheck_tool_buttons(QObject* sender); diff --git a/panels/timeline.ui b/panels/timeline.ui index af2bf631e..985a2eea3 100644 --- a/panels/timeline.ui +++ b/panels/timeline.ui @@ -245,6 +245,19 @@ + + + + Record audio + + + + :/icons/record.png + :/icons/record-disabled.png + + + + diff --git a/panels/viewer.cpp b/panels/viewer.cpp index c5e5ca61a..95d76dc1f 100644 --- a/panels/viewer.cpp +++ b/panels/viewer.cpp @@ -263,6 +263,7 @@ void Viewer::pause() { playing = false; set_playpause_icon(true); playback_updater.stop(); + stop_recording(); } void Viewer::update_playhead_timecode(long p) { diff --git a/playback/audio.cpp b/playback/audio.cpp index d9be1fbde..d5666074b 100644 --- a/playback/audio.cpp +++ b/playback/audio.cpp @@ -8,7 +8,9 @@ #include "ui_timeline.h" #include +#include #include +#include #include extern "C" { @@ -19,6 +21,9 @@ QAudioOutput* audio_output; QIODevice* audio_io_device; bool audio_device_set = false; QMutex audio_write_lock; +QAudioInput* audio_input = NULL; +QFile output_recording; +bool recording = false; qint8 audio_ibuffer[audio_ibuffer_size]; int audio_ibuffer_read = 0; @@ -177,3 +182,121 @@ int AudioSenderThread::send_audio_to_output(int offset, int max) { return actual_write; } + +void int32_to_char_array(qint32 i, char* array) { + memcpy(array, &i, 4); +} + +void write_wave_header(QFile& f, const QAudioFormat& format) { + qint32 int32bit; + char arr[4]; + + // 4 byte riff header + f.write("RIFF"); + + // 4 byte file size, filled in later + for (int i=0;i<4;i++) f.putChar(0); + + // 4 byte file type header + 4 byte format chunk marker + f.write("WAVEfmt"); + f.putChar(0x20); + + // 4 byte length of the above format data (always 16 bytes) + f.putChar(16); + for (int i=0;i<3;i++) f.putChar(0); + + // 2 byte type format (1 is PCM) + f.putChar(1); + f.putChar(0); + + // 2 byte channel count (2 for stereo) + f.putChar(2); + f.putChar(0); + + // 4 byte integer for sample rate + int32bit = format.sampleRate(); + int32_to_char_array(int32bit, arr); + f.write(arr, 4); + + // 4 byte integer for bytes per second + int32bit = (format.sampleRate() * format.sampleSize() * format.channelCount()) / 8; + int32_to_char_array(int32bit, arr); + f.write(arr, 4); + + // 2 byte integer for bytes per sample per channel + int32bit = (format.sampleSize() * format.channelCount()) / 8; + int32_to_char_array(int32bit, arr); + f.write(arr, 2); + + // 2 byte integer for bits per sample (16) + int32bit = format.sampleSize(); + int32_to_char_array(int32bit, arr); + f.write(arr, 2); + + // data chunk header + f.write("data"); + + // 4 byte integer for data chunk size (filled in later)? + for (int i=0;i<4;i++) f.putChar(0); +} + +void write_wave_trailer(QFile& f) { + char arr[4]; + + f.seek(4); + + // 4 bytes for total file size - 8 bytes + qint32 file_size = f.size() - 8; + int32_to_char_array(file_size, arr); + f.write(arr, 4); + + f.seek(40); + + // 4 bytes for data chunk size (file size - header) + file_size = f.size() - 44; + int32_to_char_array(file_size, arr); + f.write(arr, 4); +} + +bool start_recording() { + if (sequence == NULL) { + qDebug() << "[ERROR] No active sequence to record into"; + return false; + } + output_recording.setFileName("C:/Users/Matt/output.wav"); + if (!output_recording.open(QFile::WriteOnly)) { + qDebug() << "[ERROR] Failed to open output file. Does Olive have permission to write to this directory?"; + return false; + } + + QAudioFormat audio_format = audio_output->format(); + QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice(); + if (!info.isFormatSupported(audio_format)) { + qDebug() << "[WARNING] Default format not supported, using nearest"; + audio_format = info.nearestFormat(audio_format); + } + write_wave_header(output_recording, audio_format); + audio_input = new QAudioInput(audio_format); + audio_input->start(&output_recording); + recording = true; + + return true; +} + +void stop_recording() { + if (recording) { + audio_input->stop(); + + /*if (audio_input->state() == QAudio::ActiveState) { + + }*/ + + write_wave_trailer(output_recording); + + output_recording.close(); + + delete audio_input; + audio_input = NULL; + } + recording = false; +} diff --git a/playback/audio.h b/playback/audio.h index 60d75f77e..913ed0ac9 100644 --- a/playback/audio.h +++ b/playback/audio.h @@ -46,4 +46,8 @@ void init_audio(Sequence *s); void stop_audio(); int get_buffer_offset_from_frame(Sequence *s, long frame); +bool start_recording(); +void stop_recording(); +extern bool recording; + #endif // AUDIO_H diff --git a/ui/viewerwidget.cpp b/ui/viewerwidget.cpp index bbbf95d5b..dd5b74089 100644 --- a/ui/viewerwidget.cpp +++ b/ui/viewerwidget.cpp @@ -529,7 +529,8 @@ void ViewerWidget::paintGL() { glDisable(GL_TEXTURE_2D); } while (loop); - if (viewer->playing + if (!recording + && viewer->playing && (viewer->seq->playhead == viewer->seq->getEndFrame() || (viewer->seq->using_workarea && viewer->seq->playhead >= viewer->seq->workarea_out))) { viewer->pause(); }