Commit b8393f4e authored by Mikhail Karpenko's avatar Mikhail Karpenko

Stream audio over one channel only

The port number for audio streaming is passed in command-line arguments.
The sound device can also be specified on command line and default
device now is set to plughw:0,0 due to some problems with time stamps
("default" device did not produce system time stamps).
parent 3b899fc7
...@@ -55,10 +55,11 @@ ...@@ -55,10 +55,11 @@
#define SAMPLE_TIME 20 //< restrict ALSA to have this period, in milliseconds #define SAMPLE_TIME 20 //< restrict ALSA to have this period, in milliseconds
#define BUFFER_TIME 1000 //< approximate buffer duration for ALSA, in milliseconds #define BUFFER_TIME 1000 //< approximate buffer duration for ALSA, in milliseconds
#define LEN 1200 //< the size of data buffer for RTP packet, in bytes #define LEN 1200 //< the size of data buffer for RTP packet, in bytes
#define DEFAULT_SND_DEVICE "plughw:0,0" //< default sound card
using namespace std; using namespace std;
Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int channels) { Audio::Audio(int port, bool enable, Parameters *pars, string &dev_name, int sample_rate, int channels) {
snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params; snd_pcm_sw_params_t *sw_params;
_present = false; _present = false;
...@@ -101,8 +102,17 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann ...@@ -101,8 +102,17 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann
sbuffer = new short[sbuffer_len * 2 * _channels]; sbuffer = new short[sbuffer_len * 2 * _channels];
packet_buffer = new unsigned char[LEN + 20]; packet_buffer = new unsigned char[LEN + 20];
bool init_ok = false; bool init_ok = false;
if (dev_name == "")
dev_name = DEFAULT_SND_DEVICE;
while (true) { while (true) {
if ((err = snd_pcm_open(&capture_handle, "default", SND_PCM_STREAM_CAPTURE, 0)) < 0) /* Previously, "default" device was used in snd_pcm_open(), but this resulted in incorrect time stamps
* reported by snd_pcm_status_get_tstamp() (probably a bug in ALSA plugins). The time stamps did not correspond
* to system time, but rather started from 0 after reboot and then increased. Opening "plughw:0,0" device
* produces correct time stamps, thus this is the default device name now and it can be set from command line.
*/
D2(sensor_port, cerr << "Trying to open " << dev_name << endl);
if ((err = snd_pcm_open(&capture_handle, dev_name.c_str(), SND_PCM_STREAM_CAPTURE, 0)) < 0)
break; break;
snd_pcm_hw_params_alloca(&hw_params); snd_pcm_hw_params_alloca(&hw_params);
if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0) if ((err = snd_pcm_hw_params_any(capture_handle, hw_params)) < 0)
...@@ -133,11 +143,7 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann ...@@ -133,11 +143,7 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann
break; break;
if ((err = snd_pcm_sw_params_set_tstamp_mode(capture_handle, sw_params, SND_PCM_TSTAMP_ENABLE)) < 0) if ((err = snd_pcm_sw_params_set_tstamp_mode(capture_handle, sw_params, SND_PCM_TSTAMP_ENABLE)) < 0)
break; break;
/* SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY for some reason does not produce time stamps equal to system time, if ((err = snd_pcm_sw_params_set_tstamp_type(capture_handle, sw_params, SND_PCM_TSTAMP_TYPE_GETTIMEOFDAY)) < 0)
* so stick with monotonic time stamps and apply the difference between FPGA time and ALSA time stamps to
* the current samples during transmission
*/
if ((err = snd_pcm_sw_params_set_tstamp_type(capture_handle, sw_params, SND_PCM_TSTAMP_TYPE_MONOTONIC)) < 0)
break; break;
if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0) if ((err = snd_pcm_sw_params(capture_handle, sw_params)) < 0)
break; break;
...@@ -153,6 +159,7 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann ...@@ -153,6 +159,7 @@ Audio::Audio(int port, bool enable, Parameters *pars, int sample_rate, int chann
init_pthread((void *) this); init_pthread((void *) this);
} else { } else {
D(sensor_port, cerr << "Audio: init FAIL!" << endl); D(sensor_port, cerr << "Audio: init FAIL!" << endl);
D(sensor_port, cerr << "ALSA error message: " << snd_strerror(err) << endl);
_present = false; _present = false;
} }
} }
...@@ -224,8 +231,8 @@ void Audio::set_capture_volume(int nvolume) { ...@@ -224,8 +231,8 @@ void Audio::set_capture_volume(int nvolume) {
* @brief Start audio stream if it was enabled * @brief Start audio stream if it was enabled
* The time delta between FPGA time and time stamps reported by ALSA is calculated before stream is started. * The time delta between FPGA time and time stamps reported by ALSA is calculated before stream is started.
* This delta is applied to time stamps received from ALSA. Previously, this delta was calculated as time difference * This delta is applied to time stamps received from ALSA. Previously, this delta was calculated as time difference
* between FPGA time and system time reported by \e getsystimeofday(), but as for now ALSA reports monotonic * between FPGA time and system time reported by \e getsystimeofday(), but this was changed because of some problems
* time stamps regardless of the time stamp type set by \e snd_pcm_sw_params_set_tstamp_type(). See git commit * with getting system time stamps from sound device opened as "default" ("plughw" is used now). See git commit
* 58b954f239b695b7deda7a33657841d2c64476ae (or earlier) for previous implementation. * 58b954f239b695b7deda7a33657841d2c64476ae (or earlier) for previous implementation.
* @param ip ip/port pair for socket * @param ip ip/port pair for socket
* @param port ip/port pair for socket * @param port ip/port pair for socket
......
...@@ -40,7 +40,7 @@ using namespace std; ...@@ -40,7 +40,7 @@ using namespace std;
class Audio : public RTP_Stream { class Audio : public RTP_Stream {
public: public:
Audio(int port, bool enable, Parameters *pars, int sample_rate = SAMPLE_RATE, int channels = SAMPLE_CHANNELS); Audio(int port, bool enable, Parameters *pars, string &dev_name, int sample_rate = SAMPLE_RATE, int channels = SAMPLE_CHANNELS);
virtual ~Audio(void); virtual ~Audio(void);
long sample_rate(void) { return _sample_rate; }; long sample_rate(void) { return _sample_rate; };
long channels(void) { return _channels; }; long channels(void) { return _channels; };
...@@ -51,7 +51,6 @@ public: ...@@ -51,7 +51,6 @@ public:
void Start(string ip, long port, int ttl = -1); void Start(string ip, long port, int ttl = -1);
void Stop(void); void Stop(void);
protected: protected:
// int fd;
snd_pcm_t *capture_handle; //< PCM handle, returned from snd_pcm_open snd_pcm_t *capture_handle; //< PCM handle, returned from snd_pcm_open
short *sbuffer; //< buffer for sound data short *sbuffer; //< buffer for sound data
long sbuffer_len; long sbuffer_len;
...@@ -67,8 +66,6 @@ protected: ...@@ -67,8 +66,6 @@ protected:
uint64_t timestamp_rtcp; uint64_t timestamp_rtcp;
long delta_fpga_sys; //< A/V clocks delta for RTCP long delta_fpga_sys; //< A/V clocks delta for RTCP
// bool is_first;
// bool is_first2;
long long delta_fpga_alsa; //< time delta between FPGA time and time reported by ALSA, in microseconds long long delta_fpga_alsa; //< time delta between FPGA time and time reported by ALSA, in microseconds
unsigned char *packet_buffer; //< buffer for RTP packet data (header plus PCM samples) unsigned char *packet_buffer; //< buffer for RTP packet data (header plus PCM samples)
private: private:
......
...@@ -47,13 +47,33 @@ void clean_up(pthread_t *threads, size_t sz) { ...@@ -47,13 +47,33 @@ void clean_up(pthread_t *threads, size_t sz) {
} }
} }
/**
* Print help message on stdout.
* @param argv a list of command-line arguments, used to get the name of application
* @return None
*/
void print_help(char *argv[])
{
const char *msg = "Simple RTSP streamer implementation for Elphel393 series cameras.\n"
"Usage: %s [-s <port>][-D <sound_card>][-f <fps>][-h], where\n\n"
"\t-h\t\tprint this help message;\n"
"\t-s <port>\tstream sound from USB microphone over sensor port <port> channel. By default, audio streaming is not\n"
"\t\t\tenabled if this option is not specified;\n"
"\t-D <sound_card>\tuse <sound_card> device for sound stream input. The default device is plughw:0,0;\n"
"\t-f <fps>\tlimit frames per second for video streaming, works for free running mode only.\n"; // this one is processed in streamer class
printf(msg, argv[0]);
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int ret_val; int ret_val;
int audio_port = -1;
string opt; string opt;
map<string, string> args; map<string, string> args;
map<string, string>::iterator args_it;
pthread_t threads[SENSOR_PORTS]; pthread_t threads[SENSOR_PORTS];
Streamer *streamers[SENSOR_PORTS] = {NULL}; Streamer *streamers[SENSOR_PORTS] = {NULL};
// copy command-line arguments to a map container for further processing in streamer class
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
if (argv[i][0] == '-' && argv[i][1] != '\0') { if (argv[i][0] == '-' && argv[i][1] != '\0') {
if (opt != "") if (opt != "")
...@@ -72,12 +92,26 @@ int main(int argc, char *argv[]) { ...@@ -72,12 +92,26 @@ int main(int argc, char *argv[]) {
for (map<string, string>::iterator it = args.begin(); it != args.end(); it++) { for (map<string, string>::iterator it = args.begin(); it != args.end(); it++) {
cerr << "|" << (*it).first << "| == |" << (*it).second << "|" << endl; cerr << "|" << (*it).first << "| == |" << (*it).second << "|" << endl;
} }
if ((args_it = args.find("h")) != args.end()) {
print_help(argv);
exit(EXIT_SUCCESS);
} else if ((args_it = args.find("s")) != args.end()) {
audio_port = strtol(args_it->second.c_str(), NULL, 10);
// sanity check, invalid conversion produces 0 which is fine
if (audio_port < 0 || audio_port >= SENSOR_PORTS)
audio_port = -1;
}
for (int i = 0; i < 1; i++) { for (int i = 0; i < SENSOR_PORTS; i++) {
// for (int i = 0; i < SENSOR_PORTS; i++) { bool audio_en;
pthread_attr_t attr; pthread_attr_t attr;
if (i == audio_port)
audio_en = true;
else
audio_en = false;
cout << "Start thread " << i << endl; cout << "Start thread " << i << endl;
streamers[i] = new Streamer(args, i); streamers[i] = new Streamer(args, i, audio_en);
pthread_attr_init(&attr); pthread_attr_init(&attr);
ret_val = pthread_create(&threads[i], &attr, Streamer::pthread_f, (void *) streamers[i]); ret_val = pthread_create(&threads[i], &attr, Streamer::pthread_f, (void *) streamers[i]);
......
...@@ -56,16 +56,14 @@ using namespace std; ...@@ -56,16 +56,14 @@ using namespace std;
#define D2(s_port, a) #define D2(s_port, a)
#endif #endif
//Streamer *Streamer::_streamer = NULL; Streamer::Streamer(const map<string, string> &_args, int port_num, bool audio_en) {
Streamer::Streamer(const map<string, string> &_args, int port_num) {
sensor_port = port_num; sensor_port = port_num;
_streamer = this; _streamer = this;
session = new Session(); session = new Session();
params = new Parameters(sensor_port); params = new Parameters(sensor_port);
args = _args; args = _args;
audio = NULL; audio = NULL;
session->process_audio = true; session->process_audio = audio_en;
session->audio.sample_rate = 0; session->audio.sample_rate = 0;
session->audio.channels = 0; session->audio.channels = 0;
session->rtp_out.ip_custom = false; session->rtp_out.ip_custom = false;
...@@ -98,7 +96,12 @@ void Streamer::audio_init(void) { ...@@ -98,7 +96,12 @@ void Streamer::audio_init(void) {
delete audio; delete audio;
} }
D(sensor_port, cout << "audio_enabled == " << session->process_audio << endl); D(sensor_port, cout << "audio_enabled == " << session->process_audio << endl);
audio = new Audio(sensor_port, session->process_audio, params, session->audio.sample_rate, session->audio.channels); if (session->process_audio) {
string dev_name = "";
map<string, string>::iterator args_it;
if ((args_it = args.find("D")) != args.end())
dev_name = args_it->second;
audio = new Audio(sensor_port, session->process_audio, params, dev_name, session->audio.sample_rate, session->audio.channels);
if (audio->present() && session->process_audio) { if (audio->present() && session->process_audio) {
session->process_audio = true; session->process_audio = true;
session->audio.type = audio->ptype(); session->audio.type = audio->ptype();
...@@ -110,6 +113,7 @@ void Streamer::audio_init(void) { ...@@ -110,6 +113,7 @@ void Streamer::audio_init(void) {
session->audio.sample_rate = 0; session->audio.sample_rate = 0;
session->audio.channels = 0; session->audio.channels = 0;
} }
}
} }
Streamer::~Streamer(void) { Streamer::~Streamer(void) {
...@@ -278,8 +282,6 @@ int Streamer::update_settings(bool apply) { ...@@ -278,8 +282,6 @@ int Streamer::update_settings(bool apply) {
f_audio_channels = true; f_audio_channels = true;
if ((audio_proc || session->process_audio) && (f_audio_rate || f_audio_channels)) if ((audio_proc || session->process_audio) && (f_audio_rate || f_audio_channels))
audio_was_changed = true; audio_was_changed = true;
D(sensor_port, cerr << "audio_proc = " << audio_proc << ", process_audio = " << session->process_audio << ", f_audio_rate = " << f_audio_rate
<< ", f_audio_channels = " << f_audio_channels << endl);
if (apply) { if (apply) {
bool audio_restarted = false; bool audio_restarted = false;
if (audio_was_changed) { if (audio_was_changed) {
......
...@@ -33,7 +33,7 @@ using namespace std; ...@@ -33,7 +33,7 @@ using namespace std;
class Streamer { class Streamer {
public: public:
Streamer(const map<string, string> &args, int port_num); Streamer(const map<string, string> &args, int port_num, bool audio_en = false);
~Streamer(); ~Streamer();
void Main(void); void Main(void);
bool opt_present(string name) { bool opt_present(string name) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment