Skip to content

Commit

Permalink
test: pulseaudio test code work
Browse files Browse the repository at this point in the history
  • Loading branch information
TzuHuanTai committed Apr 17, 2024
1 parent 18f921e commit f2f4e90
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "debug",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${fileBasenameNoExtension}",
"program": "${workspaceFolder}/build/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
Expand Down
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ add_compile_definitions(
add_compile_options(
-pipe
-fdeclspec
-g
)

include_directories(
Expand All @@ -61,6 +62,23 @@ if(BUILD_TEST STREQUAL "recorder")
Threads::Threads
${WEBRTC_LIBRARY}
)
elseif(BUILD_TEST STREQUAL "pulseaudio")
add_executable(test_pulseaudio test/test_pulseaudio.cpp)

target_link_libraries(test_pulseaudio
pulse-simple pulse
)
elseif(BUILD_TEST STREQUAL "audio_recorder")
add_subdirectory(src/recorder)
add_subdirectory(src/v4l2_codecs)
add_subdirectory(src/common)
add_executable(test_audio_recorder test/test_audio_recorder.cpp)

target_link_libraries(test_audio_recorder
recorder
Threads::Threads
pulse-simple pulse avformat avcodec swscale avutil
)
elseif(BUILD_TEST STREQUAL "mqtt")
add_subdirectory(src/signaling)
add_executable(test_mqtt test/test_mqtt.cpp)
Expand Down
186 changes: 186 additions & 0 deletions src/recorder/audio_recorder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#include "recorder/audio_recorder.h"

#include <chrono>
#include <thread>
#include <iostream>
#include <sstream>
#include <math.h>
#include <unistd.h>

std::unique_ptr<AudioRecorder> AudioRecorder::CreateRecorder(std::string record_path) {
auto p = std::make_unique<AudioRecorder>(record_path);
p->InitializeContainer();
p->InitializeFrame();
p->InitializeFifoBuffer();
return p;
}

AudioRecorder::AudioRecorder(std::string record_path)
: encoder_name("aac"),
record_path(record_path),
frame_count(0) { }

std::string AudioRecorder::PrefixZero(int src, int digits) {
std::string str = std::to_string(src);
std::string n_zero(digits - str.length(), '0');
return n_zero + str;
}

std::string AudioRecorder::GenerateFilename() {
time_t now = time(0);
tm *ltm = localtime(&now);

std::string year = PrefixZero(1900 + ltm->tm_year, 4);
std::string month = PrefixZero(1 + ltm->tm_mon, 2);
std::string day = PrefixZero(ltm->tm_mday, 2);
std::string hour = PrefixZero(ltm->tm_hour, 2);
std::string min = PrefixZero(ltm->tm_min, 2);
std::string sec = PrefixZero(ltm->tm_sec, 2);

std::stringstream s1;
std::string filename;
s1 << year << month << day << "_" << hour << min << sec;
s1 >> filename;

return filename;
}

bool AudioRecorder::InitializeContainer() {
std::string container = "mp4";
auto filename = GenerateFilename() + "." + container;
auto full_path = record_path + '/' + filename;

if (avformat_alloc_output_context2(&fmt_ctx, nullptr,
container.c_str(),
full_path.c_str()) < 0) {
fprintf(stderr, "Could not alloc output context");
return false;
}

AddAudioStream();

if (!(fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&fmt_ctx->pb, full_path.c_str(), AVIO_FLAG_WRITE) < 0) {
fprintf(stderr, "Could not open '%s'\n", full_path.c_str());
return false;
}
}

av_dump_format(fmt_ctx, 0, full_path.c_str(), 1);

if (avformat_write_header(fmt_ctx, nullptr) < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
return false;
}

return true;
}

void AudioRecorder::AddAudioStream() {
const AVCodec *codec = avcodec_find_encoder_by_name(encoder_name.c_str());
encoder = avcodec_alloc_context3(codec);
encoder->codec_type = AVMEDIA_TYPE_AUDIO;
encoder->sample_fmt = AV_SAMPLE_FMT_FLTP;
encoder->bit_rate = 256000;
encoder->sample_rate = 48000;
encoder->channel_layout = AV_CH_LAYOUT_STEREO; //AV_CH_LAYOUT_MONO;
encoder->channels = av_get_channel_layout_nb_channels(encoder->channel_layout);
encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

st = avformat_new_stream(fmt_ctx, codec);
avcodec_parameters_from_context(st->codecpar, encoder);
avcodec_open2(encoder, codec, nullptr);
}

void AudioRecorder::InitializeFrame() {
frame = av_frame_alloc();
if (frame != nullptr) {
frame->pts = 0;
frame->nb_samples = encoder->frame_size;
frame->format = encoder->sample_fmt;
frame->channel_layout = encoder->channel_layout;
frame->sample_rate = encoder->sample_rate;
}
av_frame_get_buffer(frame, 0);
av_frame_make_writable(frame);
}

void AudioRecorder::InitializeFifoBuffer() {
fifo_buffer = av_audio_fifo_alloc(encoder->sample_fmt, encoder->channels, 1);
if (fifo_buffer == nullptr) {
printf("Init fifo fail.\n");
}
}

void AudioRecorder::Encode() {
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;

int ret = avcodec_send_frame(encoder, frame);
if (ret < 0 || ret == AVERROR_EOF) {
printf("Could not send packet for encoding\n");
return;
}

while (ret >= 0) {
ret = avcodec_receive_packet(encoder, &pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
printf("Error encoding frame\n");
break;
}

pkt.stream_index = st->index;
if ((ret = av_interleaved_write_frame(fmt_ctx, &pkt)) < 0) {
printf("Error muxing packet!");
}
av_packet_unref(&pkt);
}
}

int AudioRecorder::PushAudioBuffer(float *buffer, int total_samples, int channels) {
uint8_t **converted_input_samples = nullptr;
int samples_per_channel = total_samples/channels;
av_samples_alloc_array_and_samples(&converted_input_samples, nullptr,
encoder->channels, samples_per_channel, encoder->sample_fmt, 0);

for (int i = 0; i < total_samples; i++) {
if (i % channels >= encoder->channels) {
continue;
}
reinterpret_cast<float*>(converted_input_samples[i % channels])[i / channels] = buffer[i];
}

return av_audio_fifo_write(fifo_buffer, (void**)converted_input_samples, samples_per_channel);
}

void AudioRecorder::WriteFile() {
if (av_audio_fifo_size(fifo_buffer) < encoder->frame_size) {
return;
}

if(av_audio_fifo_read(fifo_buffer, (void**)&frame->data, encoder->frame_size) < 0) {
printf("Read fifo fail");
}

frame->pts = av_rescale_q(++frame_count * frame->nb_samples, encoder->time_base, st->time_base);

Encode();
}

void AudioRecorder::FinishFile() {
frame_count = 0;

if (fmt_ctx) {
av_write_trailer(fmt_ctx);
avio_closep(&fmt_ctx->pb);
avformat_free_context(fmt_ctx);
std::cout << "[AudioRecorder]: finished" << std::endl;
}
}

AudioRecorder::~AudioRecorder() {
FinishFile();
}
52 changes: 52 additions & 0 deletions src/recorder/audio_recorder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#ifndef AUDIO_RECODER_H_
#define AUDIO_RECODER_H_

#include <pulse/simple.h>
#include <pulse/error.h>

#include <string>
#include <future>
#include <queue>
#include <memory>

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/audio_fifo.h>
}

class AudioRecorder {
public:
static std::unique_ptr<AudioRecorder> CreateRecorder(std::string record_path);
AudioRecorder(std::string record_path);
~AudioRecorder();
int PushAudioBuffer(float *buffer, int total_samples, int channels);
void WriteFile();

protected:
double t = 0;
std::string record_path;
unsigned int frame_count;
std::string encoder_name;

AVCodecContext *encoder;
AVFormatContext *fmt_ctx;
AVStream *st;
AVAudioFifo* fifo_buffer;
AVFrame *frame;
AVPacket pkt;

std::string PrefixZero(int stc, int digits);
std::string GenerateFilename();

void Encode();
bool InitializeContainer();
void InitializeFifoBuffer();
void InitializeFrame();
void AddAudioStream();
void FinishFile();
};

#endif
66 changes: 66 additions & 0 deletions test/test_audio_recorder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// g++ -o pulseaudio-capture pulseaudio-capture.cpp -lpulse-simple -lpulse
#include <pulse/simple.h>
#include <pulse/error.h>
#include <iostream>
#include <cstring>
#include <cmath>
#include <thread>
#include <unistd.h>

#include "recorder/audio_recorder.h"

#define BUFSIZE 1024
#define CHANNELS 2
#define SAMPLE_RATE 48000

pa_simple* CreateAudioSourceObject() {
pa_simple *s = nullptr;
pa_sample_spec ss;
int error;

ss.format = PA_SAMPLE_FLOAT32LE;
ss.channels = CHANNELS;
ss.rate = SAMPLE_RATE;

s = pa_simple_new(nullptr, "Microphone", PA_STREAM_RECORD, nullptr, "record", &ss, nullptr, nullptr, &error);
if (!s) {
std::cerr << "pa_simple_new() failed: " << pa_strerror(error) << std::endl;
return nullptr;
}
return s;
}

void CaptureThread(pa_simple *src_obj, bool &abort) {
int error;
uint8_t buf[BUFSIZE * sizeof(float)];

auto recorder = AudioRecorder::CreateRecorder("./");

while (!abort) {
if (pa_simple_read(src_obj, buf, sizeof(buf), &error) < 0) {
std::cerr << "pa_simple_read() failed: " << pa_strerror(error) << std::endl;
if (src_obj) {
pa_simple_free(src_obj);
}
return;
}
recorder->PushAudioBuffer((float*)buf, sizeof(buf)/sizeof(float), CHANNELS);
recorder->WriteFile();
}
}

int main(int argc, char *argv[]) {
bool abort = false;
auto src_obj = CreateAudioSourceObject();
std::thread capture_thread(CaptureThread, src_obj, std::ref(abort));

sleep(15);
abort = true;
capture_thread.join();

if (src_obj) {
pa_simple_free(src_obj);
}

return 0;
}
Loading

0 comments on commit f2f4e90

Please sign in to comment.