mpg123_handle *our_mpg123_new(const char *decoder, int *error) { mpg123_handle *mh; const char *arch = "auto"; int x64 = 0; int rc = 0; const char *err = NULL; if (*globals.decoder || globals.outscale || globals.vol) { if (*globals.decoder) { arch = globals.decoder; } if ((mh = mpg123_new(arch, &rc))) { if (rc) { err = mpg123_plain_strerror(rc); } if (globals.outscale) { mpg123_param(mh, MPG123_OUTSCALE, globals.outscale, 0); } if (globals.vol) { mpg123_volume(mh, globals.vol); } } } else { #ifdef WIN32 x64++; #else if (sizeof(void *) == 4) { arch = "i586"; } else { x64++; } #endif if ((mh = mpg123_new(arch, &rc))) { if (rc) { err = mpg123_plain_strerror(rc); } if (x64) { mpg123_param(mh, MPG123_OUTSCALE, 8192, 0); } } } if (err) { switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error allocating mpg123 handle! %s\n", err); } return mh; }
int control_generic (mpg123_handle *fr) { struct timeval tv; fd_set fds; int n; /* ThOr */ char alive = 1; char silent = 0; /* responses to stderr for frontends needing audio data from stdout */ if (param.remote_err) outstream = stderr; else outstream = stdout; #ifndef WIN32 setlinebuf(outstream); #else /* perhaps just use setvbuf as it's C89 */ /* fprintf(outstream, "You are on Win32 and want to use the control interface... tough luck: We need a replacement for select on STDIN first.\n"); return 0; setvbuf(outstream, (char*)NULL, _IOLBF, 0); */ #endif /* the command behaviour is different, so is the ID */ /* now also with version for command availability */ fprintf(outstream, "@R MPG123 (ThOr) v8\n"); #ifdef FIFO if(param.fifo) { if(param.fifo[0] == 0) { error("You wanted an empty FIFO name??"); return 1; } #ifndef WANT_WIN32_FIFO unlink(param.fifo); if(mkfifo(param.fifo, 0666) == -1) { error2("Failed to create FIFO at %s (%s)", param.fifo, strerror(errno)); return 1; } debug("going to open named pipe ... blocking until someone gives command"); #endif /* WANT_WIN32_FIFO */ #ifdef WANT_WIN32_FIFO control_file = win32_fifo_mkfifo(param.fifo); #else control_file = open(param.fifo,O_RDONLY); #endif /* WANT_WIN32_FIFO */ debug("opened"); } #endif while (alive) { tv.tv_sec = 0; tv.tv_usec = 0; FD_ZERO(&fds); FD_SET(control_file, &fds); /* play frame if no command needs to be processed */ if (mode == MODE_PLAYING) { #ifdef WANT_WIN32_FIFO n = win32_fifo_read_peek(&tv); #else n = select(32, &fds, NULL, NULL, &tv); #endif if (n == 0) { if (!play_frame()) { /* When the track ended, user may want to keep it open (to seek back), so there is a decision between stopping and pausing at the end. */ if(param.keep_open) { mode = MODE_PAUSED; /* Hm, buffer should be stopped already, shouldn't it? */ if(param.usebuffer) out123_pause(ao); generic_sendmsg("P 1"); } else { mode = MODE_STOPPED; close_track(); generic_sendmsg("P 0"); } continue; } if (init) { print_remote_header(fr); init = 0; } if(silent == 0) { generic_sendstat(fr); if(mpg123_meta_check(fr) & MPG123_NEW_ICY) { char *meta; if(mpg123_icy(fr, &meta) == MPG123_OK) generic_sendmsg("I ICY-META: %s", meta != NULL ? meta : "<nil>"); } } } } else { /* wait for command */ while (1) { #ifdef WANT_WIN32_FIFO n = win32_fifo_read_peek(NULL); #else n = select(32, &fds, NULL, NULL, NULL); #endif if (n > 0) break; } } /* on error */ if (n < 0) { fprintf(stderr, "Error waiting for command: %s\n", strerror(errno)); return 1; } /* read & process commands */ if (n > 0) { short int len = 1; /* length of buffer */ char *cmd, *arg; /* variables for parsing, */ char *comstr = NULL; /* gcc thinks that this could be used uninitialited... */ char buf[REMOTE_BUFFER_SIZE]; short int counter; char *next_comstr = buf; /* have it initialized for first command */ /* read as much as possible, maybe multiple commands */ /* When there is nothing to read (EOF) or even an error, it is the end */ #ifdef WANT_WIN32_FIFO len = win32_fifo_read(buf,REMOTE_BUFFER_SIZE); #else len = read(control_file, buf, REMOTE_BUFFER_SIZE); #endif if(len < 1) { #ifdef FIFO if(len == 0 && param.fifo) { debug("fifo ended... reopening"); #ifdef WANT_WIN32_FIFO win32_fifo_mkfifo(param.fifo); #else close(control_file); control_file = open(param.fifo,O_RDONLY|O_NONBLOCK); #endif if(control_file < 0) { error1("open of fifo failed... %s", strerror(errno)); break; } continue; } #endif if(len < 0) error1("command read error: %s", strerror(errno)); break; } debug1("read %i bytes of commands", len); /* one command on a line - separation by \n -> C strings in a row */ for(counter = 0; counter < len; ++counter) { /* line end is command end */ if( (buf[counter] == '\n') || (buf[counter] == '\r') ) { debug1("line end at counter=%i", counter); buf[counter] = 0; /* now it's a properly ending C string */ comstr = next_comstr; /* skip the additional line ender of \r\n or \n\r */ if( (counter < (len - 1)) && ((buf[counter+1] == '\n') || (buf[counter+1] == '\r')) ) buf[++counter] = 0; /* next "real" char is first of next command */ next_comstr = buf + counter+1; /* directly process the command now */ debug1("interpreting command: %s", comstr); if(strlen(comstr) == 0) continue; /* PAUSE */ if (!strcasecmp(comstr, "P") || !strcasecmp(comstr, "PAUSE")) { if(mode != MODE_STOPPED) { if (mode == MODE_PLAYING) { mode = MODE_PAUSED; out123_pause(ao); generic_sendmsg("P 1"); } else { mode = MODE_PLAYING; out123_continue(ao); generic_sendmsg("P 2"); } } else generic_sendmsg("P 0"); continue; } /* STOP */ if (!strcasecmp(comstr, "S") || !strcasecmp(comstr, "STOP")) { if (mode != MODE_STOPPED) { /* Do we want to drop here? */ out123_drop(ao); out123_stop(ao); close_track(); mode = MODE_STOPPED; generic_sendmsg("P 0"); } else generic_sendmsg("P 0"); continue; } /* SILENCE */ if(!strcasecmp(comstr, "SILENCE")) { silent = 1; generic_sendmsg("silence"); continue; } if(!strcasecmp(comstr, "T") || !strcasecmp(comstr, "TAG")) { generic_sendalltag(fr); continue; } if(!strcasecmp(comstr, "SCAN")) { if(mode != MODE_STOPPED) { if(mpg123_scan(fr) == MPG123_OK) generic_sendmsg("SCAN done"); else generic_sendmsg("E %s", mpg123_strerror(fr)); } else generic_sendmsg("E No track loaded!"); continue; } if(!strcasecmp(comstr, "SAMPLE")) { off_t pos = mpg123_tell(fr); off_t len = mpg123_length(fr); /* I need to have portable printf specifiers that do not truncate the type... more autoconf... */ if(len < 0) generic_sendmsg("E %s", mpg123_strerror(fr)); else generic_sendmsg("SAMPLE %li %li", (long)pos, (long)len); continue; } if(!strcasecmp(comstr, "FORMAT")) { long rate; int ch; int ret = mpg123_getformat(fr, &rate, &ch, NULL); /* I need to have portable printf specifiers that do not truncate the type... more autoconf... */ if(ret < 0) generic_sendmsg("E %s", mpg123_strerror(fr)); else generic_sendmsg("FORMAT %li %i", rate, ch); continue; } if(!strcasecmp(comstr, "SHOWEQ")) { int i; generic_sendmsg("SHOWEQ {"); for(i=0; i<32; ++i) { generic_sendmsg("SHOWEQ %i : %i : %f", MPG123_LEFT, i, mpg123_geteq(fr, MPG123_LEFT, i)); generic_sendmsg("SHOWEQ %i : %i : %f", MPG123_RIGHT, i, mpg123_geteq(fr, MPG123_RIGHT, i)); } generic_sendmsg("SHOWEQ }"); continue; } if(!strcasecmp(comstr, "STATE")) { long val; generic_sendmsg("STATE {"); /* Get some state information bits and display them. */ if(mpg123_getstate(fr, MPG123_ACCURATE, &val, NULL) == MPG123_OK) generic_sendmsg("STATE accurate %li", val); generic_sendmsg("STATE }"); continue; } /* QUIT */ if (!strcasecmp(comstr, "Q") || !strcasecmp(comstr, "QUIT")) { alive = FALSE; continue; } /* some HELP */ if (!strcasecmp(comstr, "H") || !strcasecmp(comstr, "HELP")) { generic_sendmsg("H {"); generic_sendmsg("H HELP/H: command listing (LONG/SHORT forms), command case insensitve"); generic_sendmsg("H LOAD/L <trackname>: load and start playing resource <trackname>"); generic_sendmsg("H LOADPAUSED/LP <trackname>: load but do not start playing resource <trackname>"); generic_sendmsg("H LOADLIST/LL <entry> <url>: load a playlist from given <url>, and display its entries, optionally load and play one of these specificed by the integer <entry> (<0: just list, 0: play last track, >0:play track with that position in list)"); generic_sendmsg("H PAUSE/P: pause playback"); generic_sendmsg("H STOP/S: stop playback (closes file)"); generic_sendmsg("H JUMP/J <frame>|<+offset>|<-offset>|<[+|-]seconds>s: jump to mpeg frame <frame> or change position by offset, same in seconds if number followed by \"s\""); generic_sendmsg("H VOLUME/V <percent>: set volume in % (0..100...); float value"); generic_sendmsg("H RVA off|(mix|radio)|(album|audiophile): set rva mode"); generic_sendmsg("H EQ/E <channel> <band> <value>: set equalizer value for frequency band 0 to 31 on channel %i (left) or %i (right) or %i (both)", MPG123_LEFT, MPG123_RIGHT, MPG123_LR); generic_sendmsg("H EQFILE <filename>: load EQ settings from a file"); generic_sendmsg("H SHOWEQ: show all equalizer settings (as <channel> <band> <value> lines in a SHOWEQ block (like TAG))"); generic_sendmsg("H SEEK/K <sample>|<+offset>|<-offset>: jump to output sample position <samples> or change position by offset"); generic_sendmsg("H SCAN: scan through the file, building seek index"); generic_sendmsg("H SAMPLE: print out the sample position and total number of samples"); generic_sendmsg("H FORMAT: print out sampling rate in Hz and channel count"); generic_sendmsg("H SEQ <bass> <mid> <treble>: simple eq setting..."); generic_sendmsg("H PITCH <[+|-]value>: adjust playback speed (+0.01 is 1 %% faster)"); generic_sendmsg("H SILENCE: be silent during playback (meaning silence in text form)"); generic_sendmsg("H STATE: Print auxiliary state info in several lines (just try it to see what info is there)."); generic_sendmsg("H TAG/T: Print all available (ID3) tag info, for ID3v2 that gives output of all collected text fields, using the ID3v2.3/4 4-character names. NOTE: ID3v2 data will be deleted on non-forward seeks."); generic_sendmsg("H The output is multiple lines, begin marked by \"@T {\", end by \"@T }\"."); generic_sendmsg("H ID3v1 data is like in the @I info lines (see below), just with \"@T\" in front."); generic_sendmsg("H An ID3v2 data field is introduced via ([ ... ] means optional):"); generic_sendmsg("H @T ID3v2.<NAME>[ [lang(<LANG>)] desc(<description>)]:"); generic_sendmsg("H The lines of data follow with \"=\" prefixed:"); generic_sendmsg("H @T =<one line of content in UTF-8 encoding>"); generic_sendmsg("H meaning of the @S stream info:"); generic_sendmsg("H %s", remote_header_help); generic_sendmsg("H The @I lines after loading a track give some ID3 info, the format:"); generic_sendmsg("H @I ID3:artist album year comment genretext"); generic_sendmsg("H where artist,album and comment are exactly 30 characters each, year is 4 characters, genre text unspecified."); generic_sendmsg("H You will encounter \"@I ID3.genre:<number>\" and \"@I ID3.track:<number>\"."); generic_sendmsg("H Then, there is an excerpt of ID3v2 info in the structure"); generic_sendmsg("H @I ID3v2.title:Blabla bla Bla"); generic_sendmsg("H for every line of the \"title\" data field. Likewise for other fields (author, album, etc)."); generic_sendmsg("H }"); continue; } /* commands with arguments */ cmd = NULL; arg = NULL; cmd = strtok(comstr," \t"); /* get the main command */ arg = strtok(NULL,""); /* get the args */ if (cmd && strlen(cmd) && arg && strlen(arg)) { #ifndef NO_EQUALIZER /* Simple EQ: SEQ <BASS> <MID> <TREBLE> */ if (!strcasecmp(cmd, "SEQ")) { double b,m,t; int cn; if(sscanf(arg, "%lf %lf %lf", &b, &m, &t) == 3) { /* Consider adding mpg123_seq()... but also, on could define a nicer courve for that. */ if ((t >= 0) && (t <= 3)) for(cn=0; cn < 1; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, b); if ((m >= 0) && (m <= 3)) for(cn=1; cn < 2; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, m); if ((b >= 0) && (b <= 3)) for(cn=2; cn < 32; ++cn) mpg123_eq(fr, MPG123_LEFT|MPG123_RIGHT, cn, t); generic_sendmsg("bass: %f mid: %f treble: %f", b, m, t); } else generic_sendmsg("E invalid arguments for SEQ: %s", arg); continue; } /* Equalizer control :) (JMG) */ if (!strcasecmp(cmd, "E") || !strcasecmp(cmd, "EQ")) { double e; /* ThOr: equalizer is of type real... whatever that is */ int c, v; /*generic_sendmsg("%s",updown);*/ if(sscanf(arg, "%i %i %lf", &c, &v, &e) == 3) { if(mpg123_eq(fr, c, v, e) == MPG123_OK) generic_sendmsg("%i : %i : %f", c, v, e); else generic_sendmsg("E failed to set eq: %s", mpg123_strerror(fr)); } else generic_sendmsg("E invalid arguments for EQ: %s", arg); continue; } if(!strcasecmp(cmd, "EQFILE")) { equalfile = arg; if(load_equalizer(fr) == 0) generic_sendmsg("EQFILE done"); else generic_sendmsg("E failed to parse given eq file"); continue; } #endif /* SEEK to a sample offset */ if(!strcasecmp(cmd, "K") || !strcasecmp(cmd, "SEEK")) { off_t soff; off_t oldpos; off_t newpos; char *spos = arg; int whence = SEEK_SET; if(mode == MODE_STOPPED) { generic_sendmsg("E No track loaded!"); continue; } oldpos = mpg123_tell(fr); soff = (off_t) atobigint(spos); if(spos[0] == '-' || spos[0] == '+') whence = SEEK_CUR; if(0 > (soff = mpg123_seek(fr, soff, whence))) { generic_sendmsg("E Error while seeking: %s", mpg123_strerror(fr)); mpg123_seek(fr, 0, SEEK_SET); } out123_drop(ao); newpos = mpg123_tell(fr); if(newpos <= oldpos) mpg123_meta_free(fr); generic_sendmsg("K %"OFF_P, (off_p)newpos); continue; } /* JUMP */ if (!strcasecmp(cmd, "J") || !strcasecmp(cmd, "JUMP")) { char *spos; off_t offset; off_t oldpos; double secs; spos = arg; if(mode == MODE_STOPPED) { generic_sendmsg("E No track loaded!"); continue; } oldpos = framenum; if(spos[strlen(spos)-1] == 's' && sscanf(arg, "%lf", &secs) == 1) offset = mpg123_timeframe(fr, secs); else offset = atol(spos); /* totally replaced that stuff - it never fully worked a bit usure about why +pos -> spos+1 earlier... */ if (spos[0] == '-' || spos[0] == '+') offset += framenum; if(0 > (framenum = mpg123_seek_frame(fr, offset, SEEK_SET))) { generic_sendmsg("E Error while seeking"); mpg123_seek_frame(fr, 0, SEEK_SET); } out123_drop(ao); if(framenum <= oldpos) mpg123_meta_free(fr); generic_sendmsg("J %d", framenum); continue; } /* VOLUME in percent */ if(!strcasecmp(cmd, "V") || !strcasecmp(cmd, "VOLUME")) { double v; mpg123_volume(fr, atof(arg)/100); mpg123_getvolume(fr, &v, NULL, NULL); /* Necessary? */ generic_sendmsg("V %f%%", v * 100); continue; } /* PITCH (playback speed) in percent */ if(!strcasecmp(cmd, "PITCH")) { double p; if(sscanf(arg, "%lf", &p) == 1) { set_pitch(fr, ao, p); generic_sendmsg("PITCH %f", param.pitch); } else generic_sendmsg("E invalid arguments for PITCH: %s", arg); continue; } /* RVA mode */ if(!strcasecmp(cmd, "RVA")) { if(!strcasecmp(arg, "off")) param.rva = MPG123_RVA_OFF; else if(!strcasecmp(arg, "mix") || !strcasecmp(arg, "radio")) param.rva = MPG123_RVA_MIX; else if(!strcasecmp(arg, "album") || !strcasecmp(arg, "audiophile")) param.rva = MPG123_RVA_ALBUM; mpg123_volume_change(fr, 0.); generic_sendmsg("RVA %s", rva_name[param.rva]); continue; } /* LOAD - actually play */ if (!strcasecmp(cmd, "L") || !strcasecmp(cmd, "LOAD")) { generic_load(fr, arg, MODE_PLAYING); continue; } if (!strcasecmp(cmd, "LL") || !strcasecmp(cmd, "LOADLIST")) { generic_loadlist(fr, arg); continue; } /* LOADPAUSED */ if (!strcasecmp(cmd, "LP") || !strcasecmp(cmd, "LOADPAUSED")) { generic_load(fr, arg, MODE_PAUSED); continue; } /* no command matched */ generic_sendmsg("E Unknown command: %s", cmd); /* unknown command */ } /* end commands with arguments */ else generic_sendmsg("E Unknown command or no arguments: %s", comstr); /* unknown command */ } /* end of single command processing */ } /* end of scanning the command buffer */ /* when last command had no \n... should I discard it? Ideally, I should remember the part and wait for next read() to get the rest up to a \n. But that can go to infinity. Too long commands too quickly are just bad. Cannot/Won't change that. So, discard the unfinished command and have fingers crossed that the rest of this unfinished one qualifies as "unknown". */ if(buf[len-1] != 0) { char lasti = buf[len-1]; buf[len-1] = 0; generic_sendmsg("E Unfinished command: %s%c", comstr, lasti); } } /* end command reading & processing */ } /* end main (alive) loop */ debug("going to end"); /* quit gracefully */ debug("closing control"); #ifdef FIFO #if WANT_WIN32_FIFO win32_fifo_close(); #else close(control_file); /* be it FIFO or STDIN */ if(param.fifo) unlink(param.fifo); #endif /* WANT_WIN32_FIFO */ #endif debug("control_generic returning"); return 0; }
void VoxPlayer::aoplay(const std::string& path) { m_isplaying = true; ao_device* ad = NULL; mpg123_handle* mh = NULL; unsigned char* buf = NULL; size_t bufsz = 0; size_t done = 0; int driver = ao_default_driver_id(); ao_sample_format asf; int channels = 0; int encoding = 0; long rate = 0; if(!(mh = mpg123_new(NULL, NULL))) { ERROR("mpg123_new() failed"); goto error_success; } bufsz = mpg123_outblock(mh); buf = (unsigned char*)malloc(bufsz * sizeof(unsigned char)); if(mpg123_open(mh, path.c_str()) != MPG123_OK) { ERROR("mpg123_open() failed"); goto error_success; } mpg123_volume(mh, 1.4); if(mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK) { ERROR("mpg123_getformat() failed"); goto error_success; } asf.bits = mpg123_encsize(encoding) * 8; // BITS asf.rate = rate; asf.channels = channels; asf.byte_format = AO_FMT_NATIVE; asf.matrix = 0; if(!(ad = ao_open_live(driver, &asf, NULL))) { ERROR("ao_open_live() failed"); goto error_success; } while(!g_main.isExit() && !m_interrupt) { int ret = mpg123_read(mh, buf, bufsz, &done); DEBUG("ret=%d, done=%d", ret, done); if(ret == MPG123_OK || ret == MPG123_DONE || ret == MPG123_NEED_MORE) { if(!ao_play(ad, (char*)buf, done)) { ERROR("ao_play() failed"); break; } if(ret == MPG123_DONE) break; continue; } break; } DEBUG("play done"); error_success: if(buf) { free(buf); buf = NULL; } if(ad) { ao_close(ad); ad = NULL; } if(mh) { mpg123_close(mh); mpg123_delete(mh); mh = NULL; } m_isplaying = false; }
static audio_result_t init(audio_worker_t* worker, const char* file_or_url, el_bool file) { static int mp3_initialized = 0; static int mp3_error = 0; if (!mp3_initialized) { mp3_initialized = 1; if (mpg123_init() != MPG123_OK) { mp3_error = 1; } else { atexit(mpg123_exit); } } if (mp3_error) { return AUDIO_CANNOT_INITIALIZE; } mp3_t* mp3=(mp3_t*) mc_malloc(sizeof(mp3_t)); worker->can_seek = can_seek; worker->play = play; worker->pause = mp3_pause; worker->seek = seek; worker->guard = guard; worker->destroy = destroy; worker->length_in_ms = length_in_ms; worker->load_file = load_file; worker->load_url = load_url; worker->set_volume = set_volume; worker->worker_data = (void*) mp3; int error; mp3->handle = mpg123_new(NULL, &error); mp3->buffer_size = mpg123_outblock(mp3->handle); mp3->buffer = mc_malloc(mp3->buffer_size * sizeof(char)); mpg123_volume(mp3->handle, 1.0); mp3->client_notification = worker->fifo; mp3->player_control = audio_event_fifo_new(); // http streams mp3->stream_fifo = mp3_stream_fifo_new(); mp3->continue_streaming = el_true; mp3->current_block = NULL; //sem_init(&mp3->stream_ready, 0, 0); mp3->stream_ready = psem_new(0); mp3->streaming = el_false; // etc. mp3->is_open = el_false; mp3->length = -1; //sem_init(&mp3->length_set, 0, 0); mp3->length_set = psem_new(0); mp3->ao_handle = aodev_new(); if (file) { mp3->can_seek = el_true; mp3->is_file = el_true; mp3->file_or_url = mc_strdup(file_or_url); post_event(mp3->player_control, INTERNAL_CMD_LOAD_FILE, -1); } else { // URL mp3->can_seek = el_false; mp3->is_file = el_true; // we check this later mp3->file_or_url = mc_strdup(file_or_url); post_event(mp3->player_control, INTERNAL_CMD_LOAD_URL, -1); } int thread_id = pthread_create(&mp3->player_thread, NULL, player_thread, mp3); // wait until fully loaded (length is set) psem_wait(mp3->length_set); log_debug("INIT OK"); return AUDIO_OK; }
void* player_thread(void* _mp3_info) { log_debug("player thread started"); mp3_t* mp3_info = (mp3_t* ) _mp3_info; long current_position_in_ms = 0; long previous_position_in_ms = -1; long guard_position_in_ms = -1; el_bool playing = el_false; pthread_t thread_id; int no_count = 0; post_event(mp3_info->client_notification, AUDIO_READY, current_position_in_ms); audio_event_t *event; event = audio_event_fifo_dequeue(mp3_info->player_control); while (event->state != INTERNAL_CMD_DESTROY) { if (event->state != INTERNAL_CMD_NONE) { log_debug4("event = %s, %ld, %s", audio_event_name(event->state), event->position_in_ms, mp3_info->file_or_url); } audio_state_t event_state = event->state; long event_position = event->position_in_ms; audio_event_destroy(event); switch (event_state) { case INTERNAL_CMD_LOAD_FILE: { playing = el_false; // Stop stream, if playing if (!mp3_info->is_file) { mp3_info->continue_streaming = el_false; psem_wait(mp3_info->stream_ready); } if (mp3_info->is_open) { mpg123_close(mp3_info->handle); aodev_close(mp3_info->ao_handle); } mpg123_open(mp3_info->handle, mp3_info->file_or_url); mpg123_getformat(mp3_info->handle, &mp3_info->rate, &mp3_info->channels, &mp3_info->encoding); mp3_info->buffer_size = mpg123_outblock(mp3_info->handle); mc_free(mp3_info->buffer); mp3_info->buffer = mc_malloc(mp3_info->buffer_size * sizeof(char) ); int bytes_per_sample = get_encsize(mp3_info->encoding); aodev_set_format(mp3_info->ao_handle, bytes_per_sample * 8, mp3_info->rate, mp3_info->channels); aodev_open(mp3_info->ao_handle); mp3_info->is_open = el_true; mp3_info->is_file = el_true; mp3_info->can_seek = el_true; current_position_in_ms = 0; guard_position_in_ms = -1; { off_t l = mpg123_length(mp3_info->handle); if (l == MPG123_ERR) { mp3_info->length = -1; } else { mp3_info->length = (l * 1000) / mp3_info->rate; } psem_post(mp3_info->length_set); } } break; case INTERNAL_CMD_LOAD_URL: { playing = el_false; log_debug2("loading url %s", mp3_info->file_or_url); // Wait for feeding streams to end if (!mp3_info->is_file) { mp3_info->continue_streaming = el_false; psem_wait(mp3_info->stream_ready); } mp3_info->is_file = el_false; log_debug("current stream ended"); if (mp3_info->is_open) { mpg123_close(mp3_info->handle); aodev_close(mp3_info->ao_handle); mp3_info->is_open = el_false; } log_debug("aodev closed"); mpg123_open_feed(mp3_info->handle); log_debug("feed opened"); pthread_create(&thread_id, NULL, stream_thread, mp3_info); log_debug("stream thread started"); mp3_info->is_open = el_true; mp3_info->can_seek = el_false; current_position_in_ms = 0; guard_position_in_ms = -1; mp3_info->length = 0; mp3_info->continue_streaming = el_true; psem_post(mp3_info->length_set); } break; case INTERNAL_CMD_SEEK: { off_t pos = mpg123_timeframe(mp3_info->handle, (event_position / 1000.0)); mpg123_seek_frame(mp3_info->handle, pos, SEEK_SET); } break; case INTERNAL_CMD_PLAY: { playing = el_true; } break; case INTERNAL_CMD_PAUSE: { playing = el_false; } break; case INTERNAL_CMD_GUARD: { guard_position_in_ms = event_position; } break; case INTERNAL_CMD_SET_VOLUME: { double volume = ((double) event_position) / 1000.0; mpg123_volume(mp3_info->handle, volume); } case INTERNAL_CMD_NONE: break; default: break; } //log_debug3("guard = %d, playing = %d", guard_position_in_ms, playing); if (guard_position_in_ms >= 0 && current_position_in_ms >= guard_position_in_ms) { guard_position_in_ms = -1; post_event(mp3_info->client_notification, AUDIO_GUARD_REACHED, current_position_in_ms); } else if (playing) { if (mp3_info->is_file) { size_t bytes; int res = mpg123_read(mp3_info->handle, mp3_info->buffer, mp3_info->buffer_size, &bytes); if (res == MPG123_OK) { aodev_play_buffer(mp3_info->ao_handle, mp3_info->buffer, bytes); off_t frame = mpg123_tellframe(mp3_info->handle); double time_per_frame = (mpg123_tpf(mp3_info->handle)*1000.0); //static int prt = 1; //if (prt) { printf("tpf=%.6lf\n",time_per_frame);prt=0; } current_position_in_ms = (long) (frame * time_per_frame); // 1 frame is about 26 milliseconds if (previous_position_in_ms == -1) previous_position_in_ms = current_position_in_ms; if ((current_position_in_ms - previous_position_in_ms) >= STATE_REPORT_THRESHOLD) { post_event(mp3_info->client_notification, AUDIO_PLAYING, current_position_in_ms); } previous_position_in_ms = current_position_in_ms; } else if (res == MPG123_DONE) { post_event(mp3_info->client_notification, AUDIO_EOS, current_position_in_ms); playing = el_false; } else { post_event(mp3_info->client_notification, AUDIO_STATE_ERROR, current_position_in_ms); playing = el_false; } } else { // Stream playing if (mp3_stream_fifo_peek(mp3_info->stream_fifo) != NULL) { el_bool go_on = el_true; while (go_on && mp3_stream_fifo_peek(mp3_info->stream_fifo) != NULL) { memblock_t* blk = mp3_stream_fifo_dequeue(mp3_info->stream_fifo); mpg123_feed(mp3_info->handle, (const unsigned char*) memblock_as_str(blk), memblock_size(blk)); memblock_destroy(blk); size_t done; int err; unsigned char *audio; off_t frame_offset; do { err = mpg123_decode_frame(mp3_info->handle, &frame_offset, &audio, &done); switch(err) { case MPG123_NEW_FORMAT: mpg123_getformat(mp3_info->handle, &mp3_info->rate, &mp3_info->channels, &mp3_info->encoding); if (aodev_is_open(mp3_info->ao_handle)) { aodev_close(mp3_info->ao_handle); } aodev_set_format(mp3_info->ao_handle, get_encsize(mp3_info->encoding) * 8, mp3_info->rate, mp3_info->channels); aodev_open(mp3_info->ao_handle); break; case MPG123_OK: //log_debug2("playing buffer %d", done); aodev_play_buffer(mp3_info->ao_handle, audio, done); off_t frame = mpg123_tellframe(mp3_info->handle); double time_per_frame = (mpg123_tpf(mp3_info->handle)*1000.0); current_position_in_ms = (long) (frame * time_per_frame); // 1 frame is about 26 milliseconds if (previous_position_in_ms == -1) previous_position_in_ms = current_position_in_ms; if ((current_position_in_ms - previous_position_in_ms) >= STATE_REPORT_THRESHOLD) { post_event(mp3_info->client_notification, AUDIO_PLAYING, current_position_in_ms); } previous_position_in_ms = current_position_in_ms; go_on = el_false; break; case MPG123_NEED_MORE: break; default: break; } } while (done > 0); } } else { // no streaming data, prevent race conditions // sleep for a small time (50 ms); no_count += 1; if (no_count > 10) { post_event(mp3_info->client_notification, AUDIO_BUFFERING, current_position_in_ms); } sleep_ms(50); } } } if (playing) { if (audio_event_fifo_peek(mp3_info->player_control) != NULL) { event = audio_event_fifo_dequeue(mp3_info->player_control); } else { event = (audio_event_t*) mc_malloc(sizeof(audio_event_t)); event->state = INTERNAL_CMD_NONE; event->position_in_ms = -1; } } else { //log_debug("waiting for next event"); event = audio_event_fifo_dequeue(mp3_info->player_control); } } // destroy event received log_debug("destroy event received"); // Kill playing streams if (mp3_info->streaming) { mp3_info->continue_streaming = el_false; psem_wait(mp3_info->stream_ready); } audio_event_destroy(event); // exit thread return NULL; }
static int alloc_handler(struct ausrc_st **stp, const struct ausrc *as, struct media_ctx **ctx, struct ausrc_prm *prm, const char *dev, ausrc_read_h *rh, ausrc_error_h *errh, void *arg) { struct ausrc_st *st; int err; if (!stp || !as || !prm || !rh) return EINVAL; st = mem_zalloc(sizeof(*st), destructor); if (!st) return ENOMEM; st->as = as; st->rh = rh; st->errh = errh; st->arg = arg; st->mp3 = mpg123_new(NULL, &err); if (!st->mp3) { err = ENODEV; goto out; } err = mpg123_open_feed(st->mp3); if (err != MPG123_OK) { warning("rst: mpg123_open_feed: %s\n", mpg123_strerror(st->mp3)); err = ENODEV; goto out; } /* Set wanted output format */ mpg123_format_none(st->mp3); mpg123_format(st->mp3, prm->srate, prm->ch, aufmt_to_encoding(prm->fmt)); mpg123_volume(st->mp3, 0.3); st->sampc = prm->srate * prm->ch * prm->ptime / 1000; st->sampsz = aufmt_sample_size(prm->fmt); st->ptime = prm->ptime; info("rst: audio ptime=%u sampc=%zu aubuf=[%u:%u]\n", st->ptime, st->sampc, prm->srate * prm->ch * 2, prm->srate * prm->ch * 40); /* 1 - 20 seconds of audio */ err = aubuf_alloc(&st->aubuf, prm->srate * prm->ch * st->sampsz, prm->srate * prm->ch * st->sampsz * 20); if (err) goto out; if (ctx && *ctx && (*ctx)->id && !strcmp((*ctx)->id, "rst")) { st->rst = mem_ref(*ctx); } else { err = rst_alloc(&st->rst, dev); if (err) goto out; if (ctx) *ctx = (struct media_ctx *)st->rst; } rst_set_audio(st->rst, st); st->run = true; err = pthread_create(&st->thread, NULL, play_thread, st); if (err) { st->run = false; goto out; } out: if (err) mem_deref(st); else *stp = st; return err; }