Handeling large QByteArray with QtAudioOutput causes std::bad_alloc - c++

Using QAudioOut Im trying to play data that is stored in a QByteArray in a sequence... this works when there is little data being appended, however when the data gets too much, lets say a 2~3 hour RAW PCM appending from different combinations this data to a QByteArray all at once will result in a std::bad_alloc due to the heap that's not big enough to hold all the data at the same time.
I know where the problem occurs and I think I have a possible solution, its just I have no idea on how to go about implementing it.
Below is a converted function that takes the values in the list
first one 440Hz for 1800000 msec and created RAW PCM square wave. Which works then appends it to a QByteArray then plays it.
This will work if there isn't a lot of appended data form multiple added sequences.
Im looking for a way to do one at a time from the list then create the wave, play that one for x milliseconds then move on to the next entry in the MySeq list. The list can contain large sequences of 3 minute frequencies that runs for hours.
QStringList MySeq;
MySeq << "1:440:180000";
MySeq << "1:20:180000";
MySeq << "1:2120:180000";
MySeq << "1:240:180000";
MySeq << "1:570:180000";
foreach(QString seq, MySeq)
{
QStringList Assits = seq.split(":");
qDebug() << "Now At: " << seq;
QString A = Assits.at(0);
QString B = Assits.at(1);
QString C = Assits.at(2);
qreal amplitude = A.toInt();
float frequency = B.toFloat();
int msecs = C.toInt();
qreal singleWaveTime = amplitude / frequency;
qreal samplesPerWave = qCeil(format->sampleRate() * singleWaveTime);
quint32 waveCount = qCeil(msecs / (singleWaveTime * 1000.0));
quint32 sampleSize = static_cast<quint32>(format->sampleSize() / 8.0);
QByteArray data(waveCount * samplesPerWave * sampleSize * format->channelCount(), '\0');
unsigned char* dataPointer = reinterpret_cast<unsigned char*>(data.data());
for (quint32 currentWave = 0; currentWave < waveCount; currentWave++)
{
for (int currentSample = 0; currentSample < samplesPerWave; currentSample++)
{
double nextRadStep = (currentSample / static_cast<double>(samplesPerWave)) * (2 * M_PI);
quint16 sampleValue = static_cast<quint16>((qSin(nextRadStep) > 0 ? 1 : -1) * 32767);
for (int channel = 0; channel < format->channelCount(); channel++)
{
qToLittleEndian(sampleValue, dataPointer);
dataPointer += sampleSize;
}
}
}
soundBuffer->append(data); // HERE IS THE Std::Bad_Alloc
output->start(outputBuffer);
qDebug() << data.size()
}
I wish to fill the QByteArray with only one sequence at a time then Play it with QAudioOutput then clear the ByteArray then load the next sequence repeat until all sequences are done in the QStringList.
The problem with this approach now is that QAudioOutput is asynchronous and doesn't wait for the first sequence to finish
If I loop over the list as demonstrated above they load one after the other with only the last frequency actually playing. its like the loop keep overwriting the previous sequence.
Im not sure if QEventLoop (Something I haven't used yet) is needed here or Threading. I have tried a couple of different approaches with no success. Any advice would be greatly appreciated. This is a continuation of the following question I had about wave files and data and frequency generations located here
Qt C++ Creating a square audio tone wave. Play and saving it
mainWindows.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QtCore>
#include <QtMultimedia/QAudioOutput>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void playbackFinished();
private slots:
void appendSound();
void on_pushButton_Run_clicked();
void on_pushButton_Stop_clicked();
private:
Ui::MainWindow *ui;
QByteArray *soundBuffer;
QBuffer *outputBuffer;
QAudioFormat *format;
QAudioOutput *output;
};
#endif // MAINWINDOW_H
mainWindows.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
int sampleRate = 44100;
int channelCount = 2;
int sampleSize = 16;
const QString codec = "audio/pcm";
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
soundBuffer = new QByteArray();
format = new QAudioFormat();
format->setSampleRate(sampleRate);
format->setChannelCount(channelCount);
format->setSampleSize(sampleSize);
format->setCodec(codec);
format->setByteOrder(QAudioFormat::LittleEndian);
format->setSampleType(QAudioFormat::UnSignedInt);
output = new QAudioOutput(*format, this);
connect(output, SIGNAL(stateChanged(QAudio::State)), this, SLOT(playbackFinished()));
outputBuffer = new QBuffer(soundBuffer);
if (outputBuffer->open(QIODevice::ReadOnly) == false) {
qCritical() << "Invalid operation while opening QBuffer. audio/pcm";
return;
}
}
MainWindow::~MainWindow()
{
delete ui;
delete soundBuffer;
delete format;
delete output;
delete outputBuffer;
}
void MainWindow::playbackFinished()
{
if (output->state() == QAudio::IdleState)
{
qDebug() << "Playback finished";
}
}
void MainWindow::appendSound()
{
QStringList MySq;
MySq << "1:440:180000";
MySq << "1:20:180000";
MySq << "1:2120:180000";
MySq << "1:240:180000";
MySq << "1:570:180000";
MySq << "1:570:180000";
MySq << "1:570:180000";
MySq << "1:850:180000";
MySq << "1:1570:180000";
MySq << "1:200:180000";
MySq << "1:50:180000";
MySq << "1:85:180000";
MySq << "1:59:180000";
MySq << "1:20:180000";
foreach(QString seq, MySq)
{
QStringList Assits = seq.split(":");
qDebug() << "Now At: " << seq;
QString A = Assits.at(0);
QString B = Assits.at(1);
QString C = Assits.at(2);
qreal amplitude = A.toInt();
float frequency = B.toFloat();
int msecs = C.toInt();
msecs = (msecs < 50) ? 50 : msecs;
qreal singleWaveTime = amplitude / frequency;
qreal samplesPerWave = qCeil(format->sampleRate() * singleWaveTime);
quint32 waveCount = qCeil(msecs / (singleWaveTime * 1000.0));
quint32 sampleSize = static_cast<quint32>(format->sampleSize() / 8.0);
QByteArray data(waveCount * samplesPerWave * sampleSize * format->channelCount(), '\0');
unsigned char* dataPointer = reinterpret_cast<unsigned char*>(data.data());
for (quint32 currentWave = 0; currentWave < waveCount; currentWave++)
{
for (int currentSample = 0; currentSample < samplesPerWave; currentSample++)
{
double nextRadStep = (currentSample / static_cast<double>(samplesPerWave)) * (2 * M_PI);
quint16 sampleValue = static_cast<quint16>((qSin(nextRadStep) > 0 ? 1 : -1) * 32767);
for (int channel = 0; channel < format->channelCount(); channel++)
{
qToLittleEndian(sampleValue, dataPointer);
dataPointer += sampleSize;
}
}
}
soundBuffer->append(data); // <-- STD::Bad_alloc Not enough memory on heap for appending all the frequencies at once in buffer
output->start(outputBuffer);
qDebug() << data.size();
}
}
void MainWindow::on_pushButton_Run_clicked()
{
appendSound();
}
void MainWindow::on_pushButton_Stop_clicked()
{
output->stop();
soundBuffer->clear();
output->reset();
qDebug() << "Playback Stopped";
}

I came up with a solution using QThread and a Interpreter for that thread to wait for more events before continuing the next sequence in the series.
By doing this I don't need to load the full 3~4 hour PCM data into a QBufferArray all at the same time but rather break it up. Run a smaller sequence then wait on the thread to finish then load the next sequence in line and so on until all of them are done playing.
No more std::bad_alloc since the thread only uses around 80mb on the heap at any given time.

Related

C++ QT QAudioInput record files different lenght

My program is reading a configuration-file lines in a loop, and do a audio-recording for time = lines*100ms.
But for 5 same config-files I get 5 different length. (For 5sek recordings +-100ms)
I trying to change QThread::msleep(1500); or to declare QEventLoop and QTimer outside a loop, but there are no changes.
How can I get the recordings with right times?
Audio.cpp
// ************************************************************************************************
// Audio-Class
// ************************************************************************************************
#include "Audio.h"
#include "Measure.h"
#include <QAudioInput>
#include <QTimer>
// ************************************************************************************************
Audio::Audio()
{
QTimer *m_timer = new QTimer(this);
m_timer->setSingleShot(true);
AudioRecord();
}
// ************************************************************************************************
//Initialization and signal-slot connection
void Audio::AudioRecord()
{
//Set audio configuration
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(1);
format.setSampleSize(16);
format.setCodec("audio/PCM");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
//Wrong configuration detection
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
if (!info.isFormatSupported(format))
{
qWarning() << "Default format not supported";
format = info.nearestFormat(format);
}
//Signal-slot connection to show actual Audio state or errors output
m_audio = new QAudioInput(format, this);
connect(m_audio, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State)));
}
// ************************************************************************************************
//Start recording
//void Audio::StartRecording(QString rec_file_path)
void Audio::StartRecording(QString rec_file_path, quint32 record_time_msec)
{
m_file.setFileName(rec_file_path); //audio recording path format
m_file.open(QIODevice::WriteOnly); //audio access mode initialisation
writeHeader(); //writing header to convert PCM to *.wav (Step 1)
m_audio->start(&m_file); //start recording
//QTimer::singleShot((15000), this, SLOT(StopRecording()));
QTimer::singleShot((record_time_msec), this, SLOT(StopRecording()));
QCoreApplication::processEvents();
qDebug()<< m_audio->format();
//qDebug()<< m_file.open();
QAudioDeviceInfo::defaultInputDevice();
QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
//qDebug()<< m_audio->error();
//qDebug()<< m_audio->notifyInterval();
}
// ************************************************************************************************
//Write Header
// ************************************************************************************************
void Audio::writeHeader(){
quint64 data64 = 0;
quint32 data_Filesize_fill = 0;
quint32 data_lenght = 16;
quint16 data_PCM = 1;
quint16 data_Chanel = 1;
quint32 data_SamplRate = 44100;
quint32 data_Value = (44100 * 16 * 1)/8;
quint16 data_mono = (16 * 1)/8;
quint16 data_BPS = 16;
quint32 data_FileSize = 44;
m_file.write("RIFF",4);
m_file.write((char*)&data_Filesize_fill, 4);
m_file.write("WAVE",4);
m_file.write("fmt ",4); // "fmt " chunk size (always 16 for PCM)
m_file.write((char*)&data_lenght, 4);
m_file.write((char*)&data_PCM, 2);
m_file.write((char*)&data_Chanel, 2);
m_file.write((char*)&data_SamplRate, 4);
m_file.write((char*)&data_Value, 4); // bytes per second
m_file.write((char*)&data_mono, 2); // Block align
m_file.write((char*)&data_BPS, 2); // Bits per sample
m_file.write("data",4);
m_file.write((char*)&data_FileSize, 4);
m_file.flush();
}
// ************************************************************************************************
//Stop recording
void Audio::StopRecording()
{
m_audio->stop();
qDebug() << "stop";
writeHeaderFinal();
m_file.close();
qDebug() << "close";
}
// ************************************************************************************************
//Write Header
void Audio::writeHeaderFinal(){
quint32 data_Filesize_fill_after = m_file.size() - 8;
quint32 data_FileSize_after = m_file.size() - 44;
m_file.seek(4);
m_file.write((char*)&data_Filesize_fill_after, 4);
m_file.seek(40);
m_file.write((char*)&data_FileSize_after, 4);
}
// ************************************************************************************************
//Recording DEBUG output
void Audio::handleStateChanged(QAudio::State newState)
{
switch (newState)
{
case QAudio::StoppedState:
if (m_audio->error() != QAudio::NoError)
{
qDebug() << "Error audio recording!!";
} else
{
qDebug() << "Finished audio recording";
}
break;
case QAudio::ActiveState:
qDebug() << "Started audio recording";
break;
default:
break;
}
}
// ************************************************************************************************
Measure.cpp
// ************************************************************************************************
// Measure-Class
// ************************************************************************************************
//class initialisation
#include "Measure.h"
#include "Conf.h"
#include "Audio.h"
// ************************************************************************************************
Measure::Measure(Audio *audio)
{
//member variables init
m_audio = audio;
//UART-status debug
if (0 == connectSerial())
{
qDebug() << "Serial connection OK!\n";
}
else
{
qDebug() << "Serial connection Error!\n";
return;
}
auto info = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
foreach (auto i, info) qDebug() << i.deviceName();
//m_timer = new QTimer(this); double
//set serial connection
//connect(m_timer, SIGNAL(timeout()), this, SLOT(processMeas()),Qt::QueuedConnection);
measure_cnt = 0;
qDebug()<<"Starting programm";
}
// ************************************************************************************************
// Start measure
void Measure::start()
{
//QTimer *m_timer = new QTimer(this);
//m_timer->setSingleShot(true);
//m_timer->start(1000); 555
processMeas();
}
// ************************************************************************************************
// Serial connection initialization and signal-slot connection
uint32_t Measure::connectSerial(void)
{
//serial connection configuration
m_serial.setPortName("/dev/ttyACM0");
m_serial.setBaudRate(QSerialPort::Baud115200);
m_serial.setDataBits(QSerialPort::Data8);
m_serial.setParity(QSerialPort::NoParity);
m_serial.setStopBits(QSerialPort::OneStop);
m_serial.setFlowControl(QSerialPort::NoFlowControl);
if (!m_serial.open(QIODevice::ReadWrite))
return 1;
//einable serial connection
connect(&m_serial, SIGNAL(readyRead()), this, SLOT(readRequest()));
return 0;
}
// ************************************************************************************************
// State maschine
// ************************************************************************************************
void Measure::processMeas()
{
// Declaration ************************************************************
static uint32_t i = 0;
static uint32_t j = 0;
static uint8_t stm = 0;
static uint16_t m1 = 0;
static uint16_t m2 = 0;
static uint16_t m3 = 0;
static uint16_t m4 = 0;
static uint16_t var;
static QByteArray hextest;
// Read config files ***********************************************************************************
QDir directory("/home/nikitajarocky/workspace/QT/Software_2.0_QT/Config_files/");
QStringList config_files = directory.entryList(QStringList() << "*.config", QDir::Files);
config_files_cnt = config_files.count();
foreach(QString filename, config_files)
{
Conf *conf = new Conf(directory.absoluteFilePath(filename));
qDebug() << "Config file: " << filename << " is processed";
// Error message *************************
if(conf->getConf_lines().size() == 0)
{
qDebug()<<"Error conf file!!";
return;
}
// Prepare measure ****************************************************************
qDebug()<<"Debug: Prepare measure";
QStringList lines = conf->getConf_lines(); //read all rows of config files
record_time_100ms = lines.count()-9; // lines Audio config data + end lines
record_time_msec = record_time_100ms * 100; //msec conversion
for(i=0; i < (uint32_t)lines.length(); i++) //prepare single rows of config files
{
//uint32_t j = 0;
QString uart;
QString uart_hex;
if(i > 5 && i < lines.length())
{
QStringList speed_chunks = lines.at(i).split(","); //split rows by decimal point
m1 = speed_chunks.at(2).toInt();
m2 = speed_chunks.at(3).toInt();
m3 = speed_chunks.at(4).toInt();
m4 = speed_chunks.at(5).toInt();
hextest.resize(10);
hextest[0]=255;
hextest[1]=m1>>8;
hextest[2]=m1;
hextest[3]=m2>>8;
hextest[4]=m2;
hextest[5]=m3>>8;
hextest[6]=m3;
hextest[7]=m4>>8;
hextest[8]=m4;
hextest[9]=238;
qDebug()<< "Transfer data: " << i-8 << " from " << lines.count()-9 << " rows transmitted";
qDebug() << "Config file: " << filename << " is processed";
m_serial.write(hextest);
m_serial.waitForBytesWritten(1000);
QThread::usleep(50000);
}//if(lines.at(i).contains(",")) end }//for end
}//for end
//Beep
// Measurement ******************************************************************
qDebug()<<"Debug: Measurement in progress";
QEventLoop event_loop(this); //create method class QEventLoop
QTimer exit_timer(this); //create method class QTimer
exit_timer.setSingleShot(true); //set timer in mode singleShot
connect(&exit_timer, SIGNAL(timeout()), &event_loop, SLOT(quit())); //connect exit timer to event loop slot
QThread::msleep(1500);
QString path = SAVE_AUDIO_PATH+filename.left( filename.lastIndexOf( '.' ) )+"_"+QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
//QThread::msleep(1500);
m_file.setFileName(path+".log");
//m_audio->StartRecording(path+".wav");
m_audio->StartRecording(path+".wav", record_time_msec);//+0.6
//exit_timer.start(15000);
exit_timer.start(record_time_msec);//event loop timer durring measure time
event_loop.exec();
qDebug()<<"Measurement takes " << record_time_msec << " seconds";
// Stop measure ************************************
QThread::msleep(2000);
stm=0;
record_time_msec = 0;
measure_cnt++;
// All configs files done *********************************
if(measure_cnt>=config_files_cnt)
{
qDebug()<<"All measure done";
qApp->quit();
}
}//foreach
}//processMeas()
// ************************************************************************************************
// Data from STM32 receive
void Measure::readRequest()
{
m_file.open(QIODevice::WriteOnly | QIODevice::Append);
m_file.write(m_serial.readAll());
m_file.close();
}
// ************************************************************************************************
// save received data to log File
void Measure::saveFile(QByteArray buffer)
{
m_file.write(buffer);
}
Recordings are always little bit longer, so its a not stochastic error :
On the picture are 4 same config-files, two time
EDIT:
I change a code to:
m_audio->StartRecording(path+".wav", record_time_msec+5000);
exit_timer.start(record_time_msec+5000);//event loop timer
event_loop.exec();
an get:
But how can I start my measure delayed? (I want to skip beginning Sound (Buzzer))?

How to encode and decode audio data with opus?

I'm working on a voice chat and I need to compress my audio data. I record and play the audio data via the Qt Framework. If I record and play the audio data without compressing it everything is fine. If I compress,decompress and play the audio data I just hear a cracking sound.
Edit: I had a look at the demo code and I tried to use that code.
I can hear something but it is very laggy. If I increase the size of pcm_bytes to e.g 40000 it sounds better but my voice has still lags and cracking sounds.
This is the line (audioinput.cpp at the bottom):
speaker->write((const char*)pcm_bytes,3840);
codecopus.cpp:
#include "codecopus.h"
CodecOpus::CodecOpus()
{
}
void CodecOpus::initDecoder(opus_int32 samplingRate, int channels) //decoder
{
int error;
decoderState = opus_decoder_create(samplingRate,channels,&error);
if(error == OPUS_OK){
std::cout << "Created Opus Decoder struct" << std::endl;
}
}
void CodecOpus::initEncoder(opus_int32 samplingRate, int channels) // Encoder
{
int error;
encoderState = opus_encoder_create(samplingRate,channels,OPUS_APPLICATION_VOIP,&error);
error = opus_encoder_ctl(encoderState,OPUS_SET_BITRATE(64000));
if(error == OPUS_OK){
std::cout << "Created Opus Encoder struct" << std::endl;
}
}
opus_int32 CodecOpus::encodeData(const opus_int16 *pcm, int frameSize, unsigned char *data, opus_int32 maxDataBytes) //Encoder
{
opus_int32 i = opus_encode(encoderState,pcm,frameSize,data,maxDataBytes);
return i;
}
int CodecOpus::decodeData(const unsigned char *data, opus_int32 numberOfBytes,opus_int16* pcm,int frameSizeInSec) //Decoder
{
int i = opus_decode(decoderState,data,numberOfBytes,pcm,frameSizeInSec,0);
return i;
}
CodecOpus::~CodecOpus()
{
opus_decoder_destroy(this->decoderState);
opus_encoder_destroy(this->encoderState);
}
audioinput.h:
#ifndef AUDIOINPUT_H
#define AUDIOINPUT_H
#include <QAudioFormat>
#include <iostream>
#include <QAudioInput>
#include <QAudioOutput>
#include <thread>
#include "codecopus.h"
#include "QDebug"
class AudioInput : public QObject
{
Q_OBJECT
public:
AudioInput();
~AudioInput();
void startRecording();
void CreateNewAudioThread();
private:
CodecOpus opus;
unsigned char cbits[4000] = {};
opus_int16 in[960*2*sizeof(opus_int16)] = {};
opus_int16 out[5760*2] = {};
unsigned char *pcm_bytes;
int MAX_FRAME_SIZE;
QAudioFormat audioFormat;
QAudioInput *audioInput;
QIODevice *mic;
QByteArray data;
int micFrameSize;
QAudioOutput *audioOutput;
QIODevice *speaker;
QAudioFormat speakerAudioFormat;
public slots:
void OnAudioNotfiy();
};
#endif // AUDIOINPUT_H
audioinput.cpp:
#include "audioinput.h"
AudioInput::AudioInput() : audioFormat(),pcm_bytes(new unsigned char[40000])
{
audioFormat.setSampleRate(48000);
audioFormat.setChannelCount(2);
audioFormat.setSampleSize(16);
audioFormat.setSampleType(QAudioFormat::SignedInt);
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
audioFormat.setCodec("audio/pcm");
speakerAudioFormat.setSampleRate(48000);
speakerAudioFormat.setChannelCount(2);
speakerAudioFormat.setSampleSize(16);
speakerAudioFormat.setSampleType(QAudioFormat::SignedInt);
speakerAudioFormat.setByteOrder(QAudioFormat::LittleEndian);
speakerAudioFormat.setCodec("audio/pcm");
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
if(!info.isFormatSupported(audioFormat)){
std::cout << "Mic Format not supported!" << std::endl;
audioFormat = info.nearestFormat(audioFormat);
}
QAudioDeviceInfo speakerInfo = QAudioDeviceInfo::defaultOutputDevice();
if(!speakerInfo.isFormatSupported(speakerAudioFormat)){
std::cout << "Speaker Format is not supported!" << std::endl;
speakerAudioFormat = info.nearestFormat(speakerAudioFormat);
}
std::cout << speakerAudioFormat.sampleRate() << audioFormat.sampleRate() << speakerAudioFormat.channelCount() << audioFormat.channelCount() << std::endl;
audioInput = new QAudioInput(audioFormat);
audioOutput = new QAudioOutput(speakerAudioFormat);
audioInput->setNotifyInterval(20);
micFrameSize = (audioFormat.sampleRate()/1000)*20;
opus.initEncoder(audioFormat.sampleRate(),audioFormat.channelCount());
opus.initDecoder(speakerAudioFormat.sampleRate(),speakerAudioFormat.channelCount());
MAX_FRAME_SIZE = 6*960;
connect(audioInput,SIGNAL(notify()),this,SLOT(OnAudioNotfiy()));
}
AudioInput::~AudioInput()
{
}
void AudioInput::startRecording()
{
mic = audioInput->start();
speaker = audioOutput->start();
std::cout << "Recording started!" << std::endl;
}
void AudioInput::CreateNewAudioThread()
{
std::thread t1(&AudioInput::startRecording,this);
t1.detach();
}
void AudioInput::OnAudioNotfiy()
{
data = mic->readAll();
std::cout << "data size" <<data.size() << std::endl;
if(data.size() > 0){
pcm_bytes = reinterpret_cast<unsigned char*>(data.data());
//convert
for(int i=0;i<2*960;i++){ //TODO HARDCODED
in[i]=pcm_bytes[2*i+1]<<8|pcm_bytes[2*i];
}
opus_int32 compressedBytes = opus.encodeData(in,960,cbits,4000);
opus_int32 decompressedBytes = opus.decodeData(cbits,compressedBytes,out,MAX_FRAME_SIZE);
for(int i = 0; i<2*decompressedBytes;i++) //TODO HARDCODED
{
pcm_bytes[2*i]=out[i]&0xFF;
pcm_bytes[2*i+1]=(out[i]>>8)&0xFF;
}
speaker->write((const char*)pcm_bytes,3840);
}
}
1)You encode only 960 bytes, while the buffer is much larger. You must split the buffer into several equal parts and pass them to the encoder. The size of the part is 120, 240, 480, 960, 1920, and 2880.
2)Use qFromLittleEndian()/qToLittleEndian() functions or type casting when converting from char array to opus_int16 array/from opus_int16 array to char array. This will prevent cracking and poor sound quality.
Example:
void voice::slot_read_audio_input()
{
// Audio settings:
// Sample Rate=48000
// Sample Size=16
// Channel Count=1
// Byte Order=Little Endian
// Sample Type= UnSignedInt
// Encoder settings:
// Sample Rate=48000
// Channel Count=1
// OPUS_APPLICATION_VOIP
// Decoder settings:
// Sample Rate=48000
// Channel Count=1
QByteArray audio_buffer;//mic
QByteArray output_audio_buffer;//speaker
int const OPUS_INT_SIZE=2;//sizeof(opus_int16)
int const FRAME_SIZE=960;
int const MAX_FRAME_SIZE=1276;
int FRAME_COUNT=3840/FRAME_SIZE/OPUS_INT_SIZE;// 3840 is a sample size= voice_input->bytesReady;
opus_int16 input_frame[FRAME_SIZE] = {};
opus_int16 output_frame[FRAME_SIZE] = {};
unsigned char compressed_frame[MAX_FRAME_SIZE] = {};
unsigned char decompressed_frame[FRAME_SIZE*OPUS_INT_SIZE] = {};
audio_buffer.resize(voice_input->bytesReady());
output_audio_buffer.resize(FRAME_SIZE*OPUS_INT_SIZE);
input->read(audio_buffer.data(),audio_buffer.size());
for(int i=0;i<FRAME_COUNT;i++)
{
// convert from LittleEndian
for(int j=0;j<FRAME_SIZE;j++)
{
input_frame[j]=qFromLittleEndian<opus_int16>(audio_buffer.data()+j*OPUS_INT_SIZE);
// or use this:
// input_frame[j]=static_cast<short>(static_cast<unsigned char>(audio_buffer.at(OPUS_INT_SIZE*j+1))<<8|static_cast<unsigned char>(audio_buffer.at(OPUS_INT_SIZE*j)));
}
opus_int32 compressedBytes = opus_encode(enc, input_frame,FRAME_SIZE,compressed_frame,MAX_FRAME_SIZE);
opus_int32 decompressedBytes = opus_decode(dec,compressed_frame,compressedBytes,output_frame,FRAME_SIZE,0);
// conver to LittleEndian
for(int j = 0; j<decompressedBytes;j++)
{
qToLittleEndian(output_frame[j],output_audio_buffer.data()+j*OPUS_INT_SIZE);
// or use this:
// decompressed_frame[OPUS_INT_SIZE*j]=output_frame[j]&0xFF;
// decompressed_frame[OPUS_INT_SIZE*j+1]=(output_frame[j]>>8)&0xFF;
}
audio_buffer.remove(0,FRAME_SIZE*OPUS_INT_SIZE);
output->write(output_audio_buffer,FRAME_SIZE*OPUS_INT_SIZE);
// or use this:
// output->write(reinterpret_cast<char*>(decompressed_frame),FRAME_SIZE*OPUS_INT_SIZE);
}
}
I had a long answer ready about how you are misinterpreting the return value of opus.decodeData as the number of bytes, where the correct interpretation is "number of decoded samples per channel". But it still looks like you account for that in the byte conversion routine later on. So I'm not precisely sure where the bug is.
In general I think you are making the conversion from unsigned char <-> int16 more complicated than it needs to be. You should be able to just pass the audio buffer directly to / from opus and reinterpret its pointer to the needed type inline, without having to manually do bit manipulations to convert and copy between different buffers. The audio device should give you little-endian data but if there is a mismatch you can do a basic byte swapping routine
for (int c = 0; c < numSamples; c++)
{
unsigned char tmp = data[2 * c];
data[2 * c] = data[2 * c + 1];
data[2 * c + 1] = tmp;
}
I don't see it here but I assume you also have code to only consume exactly 960 samples at a time from the mic and keep the rest in the buffer for the next frame, otherwise you'll drop data.
Not that it matters much, but you can also replace 4000 in cbits with 1275, which is the maximum opus packet size.

Qt with ZeroMQ publish subscribe pattern

I would like to use ZeroMQ(4.1.2) with Qt (5.2.1).
Idea is to have zmq pub/sub (where server is outside) and sub is qt app.
Currently receive in Qt app runs once, could someone drop hint?
Should ZeroMQ receiver be implemented in some other way?
Currently my code looks like:
mainwindow.h
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
void on_pushButton_clicked();
void readZMQData();
private:
Ui::MainWindow *ui;
QSocketNotifier *qsn;
void *context;
void *subscriber;
};
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
/***** ZMQ *****/
context = zmq_ctx_new ();
subscriber = zmq_socket (context, ZMQ_SUB);
int rc = zmq_connect (subscriber, "tcp://localhost:5556");
char *filter = "";
rc = zmq_setsockopt (subscriber, ZMQ_SUBSCRIBE,filter, strlen (filter));
unsigned int fd=0;
size_t fd_size = sizeof(fd);
rc = zmq_getsockopt(subscriber,ZMQ_FD,&fd,&fd_size);
qsn = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(qsn, SIGNAL(activated(int)), this, SLOT(readZMQData()), Qt::DirectConnection);
}
MainWindow::~MainWindow()
{
zmq_close (this->subscriber);
zmq_ctx_destroy (this->context);
delete ui;
}
void MainWindow::readZMQData()
{
qsn->setEnabled(false);
qDebug() << "Got data!";
int events = 0;
std::size_t eventsSize = sizeof(events);
zmq_getsockopt(subscriber,ZMQ_EVENTS, &events, &eventsSize);
if(events & ZMQ_POLLIN){
qDebug() << " ====== Data to read ======";
char *string = s_recv(subscriber);
qDebug() << "DATA: " << string;
free(string);
}
qsn->setEnabled(true);
}
And server app is (from ZeroMQ examples):
#include "zhelpers.h"
int main (void)
{
// Prepare our context and publisher
void *context = zmq_ctx_new ();
void *publisher = zmq_socket (context, ZMQ_PUB);
int rc = zmq_bind (publisher, "tcp://*:5556");
assert (rc == 0);
// Initialize random number generator
srandom ((unsigned) time (NULL));
while (1) {
// Get values that will fool the boss
int zipcode, temperature, relhumidity;
zipcode = randof (100000);
temperature = randof (215) - 80;
relhumidity = randof (50) + 10;
// Send message to all subscribers
char update [20];
sprintf (update, "%05d %d %d", zipcode, temperature, relhumidity);
s_send (publisher, update);
}
zmq_close (publisher);
zmq_ctx_destroy (context);
return 0;
}
First tnx for helping out,
I've found the issue, when ZeroMQ notifies that there is message to read you need to read them all, not just first one.
void MainWindow::readZMQData(int fd)
{
qsn->setEnabled(false);
int events = 0;
std::size_t eventsSize = sizeof(events);
zmq_getsockopt(subscriber,ZMQ_EVENTS, &events, &eventsSize);
if(events & ZMQ_POLLIN){
qDebug() << " ====== Data to read ======";
char *string;
// THIS IS THE TRICK! READ UNTIL THERE IS MSG
while((string = s_recv_nb(subscriber)) != NULL){
qDebug() << "DATA: " << string;
free(string);
}
}
qsn->setEnabled(true);
}
The socket notifier looks like it should work. Have you read the docs on handling it properly? Especially if you are on Windows, it looks like there are special ways to handle it when doing the read... disabling, reading, etc.
http://doc.qt.io/qt-5/qsocketnotifier.html#details
Hope that helps.
As it is not clear what s_recv_nb(zmq::socket_t & socket) is, I'll provide my - slightly more detailed - implemetation of:
void MainWindow::readZMQData()
{
qsn->setEnabled(false);
int events = 0;
std::size_t eventsSize = sizeof(events);
zmq_getsockopt(subscriber,ZMQ_EVENTS, &events, &eventsSize);
if(events & ZMQ_POLLIN){
qDebug() << " ====== Data to read ======";
char *string;
int64_t more;
size_t more_size = sizeof (more);
do {
/* Create an empty ØMQ message to hold the message part */
zmq_msg_t part;
int rc = zmq_msg_init (&part);
assert (rc == 0);
rc = zmq_msg_recv (&part, subscriber, 0);
assert (rc != -1);
/* Determine if more message parts are to follow */
rc = zmq_getsockopt (subscriber, ZMQ_RCVMORE, &more, &more_size);
assert (rc == 0);
string = (char*) zmq_msg_data(&part);
qDebug() << QString(string) ; // << "more" << more;
zmq_msg_close (&part);
} while (more);
}
qsn->setEnabled(true);
}
And one more remark: in case of Windows 64 the filedescriptor fdshould be uint64_t as in zmq_getsockopt returns EINVAL on windows x64 when local address of ZMQ_FD option_val passed

Unable to record audio from microphone to QFile in Qt

I am trying to record audio from the default microphone present in my desktop and store it in QFile.
Finally I want to play the recorded file using windows media player. The issue is that QAudioInput has a method bytesReady() which always returns 0 in my case. Even if the audio state is QAudio::ActiveState the bytesReady() shows 0 bytes implying that there are no audio bytes to read from the microphone. The system microphone works just fine. My code gets perfectly compiled, I also get a file in the specifed location with 80KB size, but the file doesn't play with windows media player even if saved as a .wav, it returns an error.
The output window shows bytesReady as 0 in my case everytime. I suspect the QAudioInput is not able to read audio data from microphone.
Could you please help me find mistakes in my code?
`class mikeDemoClass : public QWidget
{
Q_OBJECT
public:
mikeDemoClass(QWidget *parent = 0, Qt::WFlags flags = 0);
~mikeDemoClass();
public slots:
void startRecording();
void browseFiles();
void stopRecording();
void handleAudioInputState(QAudio::State);
void notified();
private:
Ui::mikeDemoWidget ui;
QAudioInput *audioInput;
QFile *recordFile;
QTimer *testTimer;
int audio_state;
};
// cpp file starts here
#include <QIODevice>
#include "mic_demo.h"
mikeDemoClass::mikeDemoClass(QWidget *parent, Qt::WFlags flags)
: QWidget(parent, flags)
{
ui.setupUi(this);
audioInput = NULL;
recordFile = NULL;
audio_state = -1;
connect(ui.browseButton,SIGNAL(clicked()),this,SLOT(browseFiles()));
connect(ui.recordingButton,SIGNAL(clicked()), this,SLOT(startRecording()));
}
mikeDemoClass::~mikeDemoClass()
{
if(recordFile)
{
delete recordFile;
recordFile = NULL;
}
}
void mikeDemoClass::browseFiles()
{
QString FileName = QFileDialog::getSaveFileName(this, tr("Browse Files"), "D:/", tr("Media Files (*.raw)"));
if(!FileName.isEmpty())
{
recordFile = new QFile(FileName);
QTextDocument *textDoc = new QTextDocument(FileName);
ui.textEdit->setDocument(textDoc);
}
}
void mikeDemoClass::startRecording()
{
bool status = recordFile->open(QIODevice::WriteOnly);
if(!status)
{
qDebug() <<"Error opening the file";
}
QString default_deviceName = "";
QAudioFormat preferred_format;
QList<QAudioDeviceInfo> device_list = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
int count = device_list.count();
if(device_list.empty())
{
qDebug() <<"The Audio Input Devices is empty";
}
else
{
foreach(QAudioDeviceInfo device_info, device_list)
{
QString device_name = device_info.deviceName();
qDebug() << "device_name:" << device_name.toLatin1();
}
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
default_deviceName = info.deviceName();
if(!default_deviceName.isEmpty())
{
preferred_format = info.preferredFormat();
QString codec = preferred_format.codec();
int sampleRate = preferred_format.sampleRate();
int sampleSize = preferred_format.sampleSize();
int channelCount = preferred_format.channelCount();
int sampleType = preferred_format.sampleType();
int byteOrder = preferred_format.byteOrder();
qDebug() << "codec:" << codec.toLatin1() << "sampleRate :" << sampleRate << "sampleSize:" << sampleSize << "channel Count:" << channelCount << "sample type:" <<sampleType
<< "byteOrder:" << byteOrder;
}
}
QAudioFormat format;
format.setSampleRate(8000);
format.setChannels(1);
format.setSampleSize(8);
format.setCodec("audio/PCM");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::UnSignedInt);
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
if(!info.isFormatSupported(format))
{
qDebug() <<"Default format not supported, try to use nearest format";
format = info.nearestFormat(format);
}
audioInput = new QAudioInput(info, format, this);
connect(audioInput, SIGNAL(notify()), this, SLOT(notified()));
connect(audioInput,SIGNAL(stateChanged(QAudio::State)),this, SLOT(handleAudioInputState(QAudio::State)));
QTimer::singleShot(10000, this, SLOT(stopRecording()));
//audioInput->setBufferSize(4096);
qDebug() << "platform buffer size:" << audioInput->bufferSize();
audioInput->start(recordFile);
}
void mikeDemoClass::stopRecording()
{
testTimer->stop();
audioInput->stop();
recordFile->close();
delete audioInput;
}
void mikeDemoClass::handleAudioInputState(QAudio::State state)
{
qDebug() << "Audio State:" << state;
audio_state = state;
if(state == QAudio::StoppedState)
{
qDebug() << "Error State:" << audioInput->error();
if(audioInput->error() != QAudio::NoError)
{
qDebug() << "QAudioInput error:" << audioInput->error();
}
}
}
void mikeDemoClass::notified()
{
if(audio_state == QAudio::ActiveState)
{
qDebug() << "Error State:" << audioInput->error();
qDebug() << "platform buffer size after calling QAudioInput start():" << audioInput->bufferSize();
qDebug() << "bytesReady = " << audioInput->bytesReady()
<< ", " << "elapsedUSecs = " <<audioInput->elapsedUSecs()
<< ", " << "processedUSecs = "<<audioInput->processedUSecs();
}
}`
The output displays as follows:
device_name: "Microphone (High Definition Aud"
device_name: "default"
codec: "audio/pcm" sampleRate : 11025 sampleSize: 8 channel Count: 1 sample type: 1 byteOrder: 1
Default format not supported, try to use nearest format
platform buffer size: 0
Audio State: 0
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 1003000 , processedUSecs = 1000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 1998000 , processedUSecs = 2000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 3003000 , processedUSecs = 3000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 4000000 , processedUSecs = 4000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 5006000 , processedUSecs = 5000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 6001000 , processedUSecs = 6000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 7005000 , processedUSecs = 7000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 8002000 , processedUSecs = 8000000
Error State: 0
platform buffer size after called QAudioInput start(): 1600
bytesReady = 0 , elapsedUSecs = 8997000 , processedUSecs = 9000000
Audio State: 2
Error State: 0
Having compared your code to the example in the Qt documentation, you're using the notified signal to handle the QAudio::ActiveState, whereas the example code connects a slot to the stateChanged signal: -
void AudioInputExample::handleStateChanged(QAudio::State newState)
{
switch (newState) {
case QAudio::StoppedState:
if (audio->error() != QAudio::NoError) {
// Error handling
} else {
// Finished recording
}
break;
case QAudio::ActiveState:
// Started recording - read from IO device
break;
default:
// ... other cases as appropriate
break;
}
}
As notify() states: -
This signal is emitted when x ms of audio data has been processed the interval set by setNotifyInterval
Could the interval be zero?
However, I recommend following the example code as shown in the documentation and use the stateChanged signal, rather than notify.
Consider PHDEBUG or PHDBG as qDebug() and see the following (I think your missing point is the QIODevice which stand as a buffer.):
Reader.cpp :
#include "PhLtcReader.h"
PhLtcReader::PhLtcReader(QObject *parent) :
QObject(parent),
_input(NULL),
_position(0),
_buffer(NULL)
{
_decoder = ltc_decoder_create(1920, 3840);
PHDEBUG << "LTC Reader created";
}
bool PhLtcReader::init(QString input)
{
QList<QAudioDeviceInfo> list = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
if(list.isEmpty())
{
PHDEBUG << "No audio input device";
return false;
}
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
foreach(QAudioDeviceInfo device, list)
{
if(device.deviceName() == input)
info = device;
}
PHDEBUG << "LTC input device :" << info.deviceName();
QAudioFormat format;
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleRate(48000);
format.setChannelCount(1);
format.setSampleSize(8);
format.setSampleType(QAudioFormat::SignedInt);
if(!info.isFormatSupported(format))
{
PHDEBUG << "Unsupported audio format";
return false;
}
_position = 0;
_input = new QAudioInput(info, format);
connect(_input, SIGNAL(notify()), this, SLOT(onNotify()));
_buffer = _input->start();
_input->setNotifyInterval(10);
_pauseDetector.start();
return true;
}
void PhLtcReader::close()
{
if(_input)
{
_input->stop();
delete _buffer;
_buffer = NULL;
delete _input;
_input = NULL;
}
}
QList<QString> PhLtcReader::inputList()
{
QList<QString> names;
QList<QAudioDeviceInfo> list = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
foreach(QAudioDeviceInfo device, list)
names.append(device.deviceName());
return names;
}
void PhLtcReader::onNotify()
{
QByteArray array = _buffer->readAll();
char max = 0;
for(int i = 0; i < array.count(); i++)
{
if(array.at(i) > max)
max = array.at(i);
}
ltc_decoder_write(_decoder, (ltcsnd_sample_t*)array.data(), array.count(), _position);
LTCFrameExt frame;
unsigned int *hhmmssff = new unsigned int[4];
while(ltc_decoder_read(_decoder, &frame))
{
PhFrame oldFrame = _clock.frame();
hhmmssff[0] = frame.ltc.hours_tens * 10 + frame.ltc.hours_units;
hhmmssff[1] = frame.ltc.mins_tens * 10 + frame.ltc.mins_units;
hhmmssff[2] = frame.ltc.secs_tens * 10 + frame.ltc.secs_units;
hhmmssff[3] = frame.ltc.frame_tens * 10 + frame.ltc.frame_units;
}
delete hhmmssff;
_position += array.count();
}
Reader.h
class PhLtcReader : public QObject
{
Q_OBJECT
public:
explicit PhLtcReader(QObject *parent = 0);
bool init(QString _input="");
void close();
static QList<QString> inputList();
private slots:
void onNotify();
private:
QAudioInput *_input;
qint64 _position;
QIODevice * _buffer;
};
#endif // PHLTCREADER_H
The answer to your question can be found in the output.
There is a line that says
Default format not supported, try to use nearest format
Which is the problem. The format you're using isn't supported by your platform. Try a different one or install the codecs/drivers to support that format.

Qt OpenCv "VIDIOC_QUERYCTRL: Input/output error" causing application shutdown

I am working on USB WebCamera using OpenCv in Qt.The code works fine for taking images, saving images etc. But randomly it generates error "VIDIOC_QUERYCTRL: Input/output error" causing my whole qt application to shutdown.
Actually The Scenario of my application is as follow:
In MainWindow.c, I have a pushbutton, which is when pressed starts the camera:
void MainWindow::on_pushButton_continue_pressed()
{
camcapture = new CamCapture( imgResolution, defaultPath, this);
connect(camcapture, SIGNAL(imgChange(QString)), this, SLOT(changeImg(QString)));
camcapture->show();
}
and Here is piece of my CamCapture Class:
CamCapture::CamCapture( int Resolution, QString path, QWidget *parent) :
QFrame(parent),
ui(new Ui::CamCapture), timer_(this), counter_(0)
{
ui->setupUi(this);
/*----some variable initialization---*/
camInit();
timer_.start(100);
connect(&timer_,SIGNAL(timeout()), this,SLOT(captureLoop()));
}
void CamCapture::camInit()
{
capture_ = cvCaptureFromCAM(0);
if (!capture_)
{
qDebug()<<"cannot get webcam...";
ui->label_image->setText("No webcam!");
ui->label_image->setAlignment(Qt::AlignCenter);
buttonActiveStatus( false, false, false );
return;
}
cvGrabFrame(capture_); // Grabs frame from camera or file
image_ = cvRetrieveFrame(capture_); // Gets the image grabbed with cvGrabFrame
qDebug() << "image size : " << image_->width << "x" << image_->height;
qDebug() << "\n" << ui->label_image->x();
qDebug() << "\n" << ui->label_image->y();
}
QImage CamCapture::Ipl2QImage(const IplImage *newImage)
{
QImage qtemp;
if (newImage && cvGetSize(newImage).width > 0)
{
int x;
int y;
char* data = newImage->imageData;
qtemp= QImage(newImage->width, newImage->height,QImage::Format_RGB32 );
for( y = 0; y < newImage->height; y++, data +=newImage->widthStep )
for( x = 0; x < newImage->width; x++)
{
uint *p = (uint*)qtemp.scanLine (y) + x;
*p = qRgb(data[x * newImage->nChannels+2],
data[x * newImage->nChannels+1],data[x * newImage- >nChannels]);
}
}
return qtemp;
}
void CamCapture::captureLoop()
{
if ( !capture_ )
{
return;
}
cvGrabFrame(capture_);
image_ = cvRetrieveFrame(capture_);
if (image_)
{
counter_ ++;
qImage_ = Ipl2QImage(image_);
ui->label_image- >setPixmap(QPixmap::fromImage(qImage_.scaled(width(),height()),Qt::AutoColor));
}
}
void CamCapture::on_pushButton_cancel_pressed()
{
cvReleaseCapture( &capture_ );
cvDestroyAllWindows();
delay(100);
qDebug() << "nb frames :" << counter_;
CamCapture::close();
}
void CamCapture::on_pushButton_capture_pressed()
{
savePath = _imgPath;
timer_.stop();
frame = cvQueryFrame(capture_); // Gets the image grabbed with cvGrabFrame
}
void CamCapture::on_pushButton_retake_pressed()
{
timer_.start(100);
captureLoop();
}
void CamCapture::on_pushButton_save_pressed()
{
int iwidth = _imgWidth * _imgResolution * scaledFactor;
int iheight = _imgHeight * _imgResolution * scaledFactor;
QString str = savePath;
IplImage *small;
small = cvCreateImage(cvSize(iwidth,iheight), 8, 3);
cvResize(frame, small);
cvSaveImage(savePath.toUtf8().constData(),small);
delay(100);
_imgNum++;
emit imgChange(savePath);
on_pushButton_cancel_pressed();
}
The above code randomly generates error ( causing my application shutdowm ) as follow:
Debugging starts
&"warning: GDB: Failed to set controlling terminal: Invalid argument\n"
Corrupt JPEG data: bad Huffman code
image size : 640 x 480
5
10
nb frames : 11
VIDIOC_QUERYCTRL: Input/output error
Debugging has finished
Commonly, the Error is generated Either while pressing pushButton_continue from MainWindow.c or While pressing pushButton_Save from CamCapture.c
Unable to find any solution on google.
Need some suggestions/help/link to solve this issue.
Thanks in advance.