5 * Copyright 2006, Alejandro Vaquero <alejandrovaquero@yahoo.com>
7 * Wireshark - Network traffic analyzer
8 * By Gerald Combs <gerald@wireshark.org>
9 * Copyright 1999 Gerald Combs
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 * Here is a summary on how this works:
28 * - The VoipCalls will call add_rtp_packet() every time there is an RTP
30 * - add_rtp_packet() will add the RTP packet in a RTP stream struct, and
31 * create the RTP stream if it is the first RTP packet in the stream.
32 * - Each new RTP stream will be added to a list of RTP streams, called
34 * - When the user clicks "Player" in the VoipCall dialogue,
35 * rtp_player_init() is called.
36 * - rtp_player_init() creates the main dialog, and it calls:
37 * + mark_rtp_stream_to_play() to mark the RTP streams that needs to be
38 * displayed. These are the RTP streams that match the selected calls in
40 * + decode_rtp_stream() this will decode the RTP packets in each RTP
41 * stream, and will also create the RTP channels. An RTP channel is a
42 * group of RTP streams that have in common the source and destination
43 * IP and UDP ports. The RTP channels is what the user will listen in
44 * one of the two Audio channels.
45 * The RTP channels are stored in the hash table rtp_channels_hash
46 * + add_channel_to_window() will create and add the Audio graphic
47 * representation in the main window
48 * - When the user clicks the check box to listen one of the Audio channels,
49 * the structure rtp_channels is filled to play one or two RTP channels
50 * (a max of two channels can be listened at a given moment)
58 #ifdef HAVE_LIBPORTAUDIO
61 #include "portaudio.h"
65 #include <epan/stats_tree.h>
66 #include <epan/addr_resolv.h>
67 #include <epan/dissectors/packet-rtp.h>
68 #include <epan/rtp_pt.h>
69 #include <epan/codecs.h>
70 #include <epan/prefs.h>
72 #include "../globals.h"
73 #include "../simple_dialog.h"
74 #include "../codecs/G711a/G711adecode.h"
75 #include "../codecs/G711u/G711udecode.h"
77 #include "ui/gtk/gui_utils.h"
78 #include "ui/gtk/dlg_utils.h"
79 #include "ui/gtk/graph_analysis.h"
80 #include "ui/gtk/voip_calls_dlg.h"
81 #include "ui/gtk/voip_calls.h"
82 #include "ui/gtk/gtkglobals.h"
83 #include "ui/gtk/rtp_player.h"
84 #include "ui/gtk/stock_icons.h"
86 #include "ui/gtk/old-gtk-compat.h"
88 /*define this symbol to compile with G729 and G723 codecs*/
89 /*#define HAVE_G729_G723 1*/
92 #include "codecs/G729/G729decode.h"
93 #include "codecs/G723/G723decode.h"
94 #endif /* HAVE_G729_G723 */
96 static gboolean initialized = FALSE;
98 static voip_calls_tapinfo_t *voip_calls = NULL;
100 /* Hash table with all the RTP streams */
101 static GHashTable* rtp_streams_hash = NULL;
103 /* List with all the RTP streams (this is used to decode them as it is sorted)*/
104 static GList* rtp_streams_list = NULL;
107 static GtkWidget *rtp_player_dlg_w;
108 static GtkWidget *channels_vb;
109 static GtkWidget *main_scrolled_window = NULL;
110 static GtkWidget *jitter_spinner;
111 static GtkWidget *cb_use_rtp_timestamp;
112 static GtkWidget *cb_view_as_time_of_day;
113 static GtkWidget *bt_decode;
114 static GtkWidget *bt_play;
115 static GtkWidget *bt_pause;
116 static GtkWidget *bt_stop;
117 static GtkWidget *progress_bar;
118 static GtkWidget *info_bar;
119 static GtkWidget *stat_hbox;
121 static guint32 total_packets;
122 static guint32 total_frames;
123 static guint32 progbar_count;
125 static int new_jitter_buff;
127 /* a hash table with the RTP streams to play per audio channel */
128 static GHashTable *rtp_channels_hash = NULL;
130 /* Port Audio stuff */
131 #define SAMPLE_RATE (8000)
132 #define NUM_CHANNELS (2)
134 #define PA_SAMPLE_TYPE paInt16
135 typedef gint16 SAMPLE;
136 #define SAMPLE_SILENCE (0)
137 #define FRAMES_PER_BUFFER (512)
139 typedef struct _sample_t {
145 #define S_DROP_BY_JITT 1
146 #define S_WRONG_SEQ 2
147 #define S_WRONG_TIMESTAMP 3 /* The timestamp does not reflect the number of samples - samples have been dropped or silence inserted to match timestamp */
148 #define S_SILENCE 4 /* Silence inserted by Wireshark, rather than contained in a packet */
150 /* Display channels constants */
152 #define CHANNEL_WIDTH 500
153 #define CHANNEL_HEIGHT 100
154 #define MAX_TIME_LABEL 10
155 #define HEIGHT_TIME_LABEL 18
156 #define MAX_NUM_COL_CONV 10
159 static PortAudioStream *pa_stream;
160 #else /* PORTAUDIO_API_1 */
161 static PaStream *pa_stream;
162 #endif /* PORTAUDIO_API_1 */
164 /* defines a RTP stream */
165 typedef struct _rtp_stream_info {
171 guint32 first_frame_number; /* first RTP frame for the stream */
172 double start_time; /* RTP stream start time in ms */
173 nstime_t start_time_abs;
176 GList* rtp_packets_list; /* List of RTP packets in the stream */
181 /* defines the RTP streams to be played in an audio channel */
182 typedef struct _rtp_channel_info {
183 double start_time; /* RTP stream start time in ms */
184 nstime_t start_time_abs;
185 double end_time; /* RTP stream end time in ms */
186 GArray *samples; /* the array with decoded audio */
190 guint32 drop_by_jitter_buff;
192 guint32 wrong_timestamp;
193 guint32 max_frame_index;
195 GtkWidget *separator;
196 GtkWidget *scroll_window;
197 GtkWidget *draw_area;
198 #if GTK_CHECK_VERSION(2,22,0)
199 cairo_surface_t *surface;
203 GtkAdjustment *h_scrollbar_adjustment;
204 GdkPixbuf* cursor_pixbuf;
206 PaTimestamp cursor_prev;
207 #else /* PORTAUDIO_API_1 */
209 #endif /* PORTAUDIO_API_1 */
210 GdkColor bg_color[MAX_NUM_COL_CONV+1];
211 gboolean cursor_catch;
212 rtp_stream_info_t *first_stream; /* This is the first RTP stream in the channel */
214 } rtp_channel_info_t;
216 /* defines a RTP packet */
217 typedef struct _rtp_packet {
218 struct _rtp_info *info; /* the RTP dissected info */
219 double arrive_offset; /* arrive offset time since the begining of the stream in ms */
220 guint8* payload_data;
223 /* defines the two RTP channels to be played */
224 typedef struct _rtp_play_channles {
225 rtp_channel_info_t* rci[2]; /* Channels to be played */
226 guint32 start_index[2];
227 guint32 end_index[2];
229 guint32 max_frame_index;
233 gint32 pause_duration;
235 PaTimestamp out_diff_time;
236 #else /* PORTAUDIO_API_1 */
237 PaTime out_diff_time;
238 PaTime pa_start_time;
239 #endif /* PORTAUDIO_API_1 */
240 } rtp_play_channels_t;
242 /* The two RTP channels to play */
243 static rtp_play_channels_t *rtp_channels = NULL;
245 typedef struct _rtp_decoder_t {
246 codec_handle_t handle;
251 /****************************************************************************/
253 rtp_key_destroy(gpointer key)
259 /****************************************************************************/
261 rtp_channel_value_destroy(gpointer rci_arg)
263 rtp_channel_info_t *rci = rci_arg;
265 g_array_free(rci->samples, TRUE);
270 /****************************************************************************/
272 rtp_stream_value_destroy(gpointer rsi_arg)
274 rtp_stream_info_t *rsi = rsi_arg;
275 GList* rtp_packets_list;
278 rtp_packets_list = g_list_first(rsi->rtp_packets_list);
279 while (rtp_packets_list)
281 rp = rtp_packets_list->data;
284 g_free(rp->payload_data);
288 rtp_packets_list = g_list_next(rtp_packets_list);
290 g_free((void *)(rsi->src_addr.data));
291 g_free((void *)(rsi->dest_addr.data));
296 /****************************************************************************/
298 rtp_decoder_value_destroy(gpointer dec_arg)
300 rtp_decoder_t *dec = dec_arg;
303 codec_release(dec->handle, dec->context);
307 /****************************************************************************/
309 set_sensitive_check_bt(gchar *key _U_ , rtp_channel_info_t *rci, guint *stop _U_ )
311 gtk_widget_set_sensitive(rci->check_bt, !(*stop));
314 /****************************************************************************/
316 bt_state(gboolean decode, gboolean play, gboolean pause, gboolean stop)
318 gboolean new_jitter_value = FALSE;
319 gboolean false_val = FALSE;
321 gtk_widget_set_sensitive(bt_decode, decode);
322 gtk_widget_set_sensitive(cb_use_rtp_timestamp, decode);
323 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_use_rtp_timestamp))) {
324 gtk_widget_set_sensitive(jitter_spinner, FALSE);
326 gtk_widget_set_sensitive(jitter_spinner, decode);
329 if (new_jitter_buff != (int) gtk_spin_button_get_value((GtkSpinButton * )jitter_spinner)) {
330 new_jitter_value = TRUE;
333 /* set the sensitive state of play only if there is a channel selected */
334 if ( play && (rtp_channels->rci[0] || rtp_channels->rci[1]) && !new_jitter_value) {
335 gtk_widget_set_sensitive(bt_play, TRUE);
337 gtk_widget_set_sensitive(bt_play, FALSE);
340 if (!new_jitter_value) {
341 gtk_widget_set_sensitive(bt_pause, pause);
342 gtk_widget_set_sensitive(bt_stop, stop);
344 /* Set sensitive to the check buttons based on the STOP state */
345 if (rtp_channels_hash)
346 g_hash_table_foreach( rtp_channels_hash, (GHFunc)set_sensitive_check_bt, &stop);
348 gtk_widget_set_sensitive(bt_pause, FALSE);
349 gtk_widget_set_sensitive(bt_stop, FALSE);
351 if (rtp_channels_hash)
352 g_hash_table_foreach( rtp_channels_hash, (GHFunc)set_sensitive_check_bt, &false_val);
356 /****************************************************************************/
358 add_rtp_packet(const struct _rtp_info *rtp_info, packet_info *pinfo)
360 rtp_stream_info_t *stream_info = NULL;
361 rtp_packet_t *new_rtp_packet;
362 GString *key_str = NULL;
364 /* create the streams hash if it doen't exist */
365 if (!rtp_streams_hash)
366 rtp_streams_hash = g_hash_table_new_full( g_str_hash, g_str_equal, rtp_key_destroy, rtp_stream_value_destroy);
368 /* Create a hash key to lookup in the RTP streams hash table
369 * uses: src_ip:src_port dst_ip:dst_port ssrc
371 key_str = g_string_new("");
372 g_string_printf(key_str, "%s:%d %s:%d %d", get_addr_name(&(pinfo->src)),
373 pinfo->srcport, get_addr_name(&(pinfo->dst)),
374 pinfo->destport, rtp_info->info_sync_src );
376 /* lookup for this RTP packet in the stream hash table */
377 stream_info = g_hash_table_lookup( rtp_streams_hash, key_str->str);
379 /* if it is not in the hash table, create a new stream */
380 if (stream_info==NULL) {
381 stream_info = g_malloc(sizeof(rtp_stream_info_t));
382 COPY_ADDRESS(&(stream_info->src_addr), &(pinfo->src));
383 stream_info->src_port = pinfo->srcport;
384 COPY_ADDRESS(&(stream_info->dest_addr), &(pinfo->dst));
385 stream_info->dest_port = pinfo->destport;
386 stream_info->ssrc = rtp_info->info_sync_src;
387 stream_info->rtp_packets_list = NULL;
388 stream_info->first_frame_number = pinfo->fd->num;
389 stream_info->start_time = nstime_to_msec(&pinfo->fd->rel_ts);
390 stream_info->start_time_abs = pinfo->fd->abs_ts;
391 stream_info->call_num = 0;
392 stream_info->play = FALSE;
393 stream_info->num_packets = 0;
395 g_hash_table_insert(rtp_streams_hash, g_strdup(key_str->str), stream_info);
397 /* Add the element to the List too. The List is used to decode the packets because it is sorted */
398 rtp_streams_list = g_list_append(rtp_streams_list, stream_info);
401 /* increment the number of packets in this stream, this is used for the progress bar and statistics */
402 stream_info->num_packets++;
404 /* Add the RTP packet to the list */
405 new_rtp_packet = g_malloc(sizeof(rtp_packet_t));
406 new_rtp_packet->info = g_malloc(sizeof(struct _rtp_info));
408 memcpy(new_rtp_packet->info, rtp_info, sizeof(struct _rtp_info));
409 new_rtp_packet->arrive_offset = nstime_to_msec(&pinfo->fd->rel_ts) - stream_info->start_time;
410 /* copy the RTP payload to the rtp_packet to be decoded later */
411 if (rtp_info->info_all_data_present && (rtp_info->info_payload_len != 0)) {
412 new_rtp_packet->payload_data = g_malloc(rtp_info->info_payload_len);
413 memcpy(new_rtp_packet->payload_data, &(rtp_info->info_data[rtp_info->info_payload_offset]), rtp_info->info_payload_len);
415 new_rtp_packet->payload_data = NULL;
418 stream_info->rtp_packets_list = g_list_append(stream_info->rtp_packets_list, new_rtp_packet);
420 g_string_free(key_str, TRUE);
423 /****************************************************************************/
424 /* Mark the RTP stream to be played. Use the voip_calls graph to see if the
425 * setup_frame is there and then if the associated voip_call is selected.
428 mark_rtp_stream_to_play(gchar *key _U_ , rtp_stream_info_t *rsi, gpointer ptr _U_)
431 graph_analysis_item_t *graph_item;
432 GList* voip_calls_list;
433 voip_calls_info_t *tmp_voip_call;
435 /* Reset the "to be play" value because the user can close and reopen the RTP Player window
436 * and the streams are not reset in that case
440 /* and associate the RTP stream with a call using the first RTP packet in the stream */
441 graph_list = g_list_first(voip_calls->graph_analysis->list);
444 graph_item = graph_list->data;
445 if (rsi->first_frame_number == graph_item->fd->num) {
446 rsi->call_num = graph_item->conv_num;
447 /* if it is in the graph list, then check if the voip_call is selected */
448 voip_calls_list = g_list_first(voip_calls->callsinfo_list);
449 while (voip_calls_list)
451 tmp_voip_call = voip_calls_list->data;
452 if ( (tmp_voip_call->call_num == rsi->call_num) && (tmp_voip_call->selected == TRUE) ) {
454 total_packets += rsi->num_packets;
457 voip_calls_list = g_list_next(voip_calls_list);
461 graph_list = g_list_next(graph_list);
465 /****************************************************************************/
466 /* Mark the ALL RTP stream to be played. This is called when calling the
467 * RTP player from the "RTP Analysis" window
470 mark_all_rtp_stream_to_play(gchar *key _U_ , rtp_stream_info_t *rsi, gpointer ptr _U_)
473 total_packets += rsi->num_packets;
476 /****************************************************************************/
477 /* Decode a RTP packet
478 * Return the number of decoded bytes
481 decode_rtp_packet(rtp_packet_t *rp, SAMPLE **out_buff, GHashTable *decoders_hash)
483 unsigned int payload_type;
485 rtp_decoder_t *decoder;
486 SAMPLE *tmp_buff = NULL;
488 int decoded_bytes = 0;
490 if ((rp->payload_data == NULL) || (rp->info->info_payload_len == 0) ) {
494 payload_type = rp->info->info_payload_type;
496 /* Look for registered codecs */
497 decoder = g_hash_table_lookup(decoders_hash, GUINT_TO_POINTER(payload_type));
498 if (!decoder) { /* Put either valid or empty decoder into the hash table */
499 decoder = g_malloc(sizeof(rtp_decoder_t));
500 decoder->handle = NULL;
501 decoder->context = NULL;
502 p = match_strval_ext(payload_type, &rtp_payload_type_short_vals_ext);
504 decoder->handle = find_codec(p);
506 decoder->context = codec_init(decoder->handle);
508 g_hash_table_insert(decoders_hash, GUINT_TO_POINTER(payload_type), decoder);
510 if (decoder->handle) { /* Decode with registered codec */
511 tmp_buff_len = codec_decode(decoder->handle, decoder->context, rp->payload_data, rp->info->info_payload_len, NULL, NULL);
512 tmp_buff = g_malloc(tmp_buff_len);
513 decoded_bytes = codec_decode(decoder->handle, decoder->context, rp->payload_data, rp->info->info_payload_len, tmp_buff, &tmp_buff_len);
514 *out_buff = tmp_buff;
515 return decoded_bytes;
518 /* Try to decode with built-in codec */
520 switch (payload_type) {
522 case PT_PCMU: /* G.711 u-law */
523 tmp_buff = g_malloc(sizeof(SAMPLE) * rp->info->info_payload_len * 1);
524 decodeG711u(rp->payload_data, rp->info->info_payload_len,
525 tmp_buff, &decoded_bytes);
528 case PT_PCMA: /* G.711 A-law */
529 tmp_buff = g_malloc(sizeof(SAMPLE) * rp->info->info_payload_len * 1);
530 decodeG711a(rp->payload_data, rp->info->info_payload_len,
531 tmp_buff, &decoded_bytes);
534 #ifdef HAVE_G729_G723
535 case PT_G729: /* G.729 */
536 /* G729 8kbps => 64kbps/8kbps = 8 */
537 /* Compensate for possible 2 octet SID frame (G.729B) */
538 tmp_buff = g_malloc(sizeof(SAMPLE) * ((rp->info->info_payload_len + 8) / 10) * 80);
539 decodeG729(rp->payload_data, rp->info->info_payload_len,
540 tmp_buff, &decoded_bytes);
543 case PT_G723: /* G.723 */
544 if (rp->info->info_payload_len%24 == 0) /* G723 High 6.4kbps */
545 tmp_buff = g_malloc(sizeof(SAMPLE) * rp->info->info_payload_len * 10); /* G723 High 64kbps/6.4kbps = 10 */
546 else if (rp->info->info_payload_len%20 == 0) /* G723 Low 5.3kbps */
547 tmp_buff = g_malloc(sizeof(SAMPLE) * rp->info->info_payload_len * 13); /* G723 High 64kbps/5.3kbps = 13 */
551 decodeG723(rp->payload_data, rp->info->info_payload_len,
552 tmp_buff, &decoded_bytes);
554 #endif /* HAVE_G729_G723 */
558 * XXX - return an error here, so the user gets told that
559 * we don't support this codec!
564 *out_buff = tmp_buff;
565 return decoded_bytes;
568 /****************************************************************************/
570 update_progress_bar(gfloat fraction)
573 if GTK_IS_PROGRESS_BAR(progress_bar)
574 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress_bar), fraction);
576 /* Force gtk to redraw the window before starting decoding the packet */
577 while (gtk_events_pending())
578 gtk_main_iteration();
581 /****************************************************************************/
582 /* Decode the RTP streams and add them to the RTP channels struct
585 decode_rtp_stream(rtp_stream_info_t *rsi, gpointer ptr _U_)
587 GString *key_str = NULL;
588 rtp_channel_info_t *rci;
589 gboolean first = TRUE;
590 GList* rtp_packets_list;
595 double rtp_time_prev;
597 double arrive_time_prev;
599 double start_rtp_time = 0;
602 #ifdef DEBUG /* ?? */
604 double total_time_prev;
606 gint32 silence_frames;
610 #ifdef DEBUG /* ?? */
615 int decoded_bytes_prev;
617 SAMPLE *out_buff = NULL;
621 guint32 start_timestamp;
622 GHashTable *decoders_hash = NULL;
624 guint32 progbar_nextstep;
629 silence.status = S_NORMAL;
631 /* skip it if we are not going to play it */
632 if (rsi->play == FALSE) {
636 /* get the static jitter buffer from the spinner gui */
637 jitter_buff = (int) gtk_spin_button_get_value((GtkSpinButton * )jitter_spinner);
639 /* Create a hash key to lookup in the RTP channels hash
640 * uses: src_ip:src_port dst_ip:dst_port call_num
642 key_str = g_string_new("");
643 g_string_printf(key_str, "%s:%d %s:%d %d", get_addr_name(&(rsi->src_addr)),
644 rsi->src_port, get_addr_name(&(rsi->dest_addr)),
645 rsi->dest_port, rsi->call_num );
647 /* create the rtp_channels_hash table if it doesn't exist */
648 if (!rtp_channels_hash) {
649 rtp_channels_hash = g_hash_table_new_full( g_str_hash, g_str_equal, rtp_key_destroy, rtp_channel_value_destroy);
652 /* lookup for this stream in the channel hash table */
653 rci = g_hash_table_lookup( rtp_channels_hash, key_str->str);
655 /* ..if it is not in the hash, create an entry */
657 rci = g_malloc(sizeof(rtp_channel_info_t));
658 rci->call_num = rsi->call_num;
659 rci->start_time = rsi->start_time;
660 rci->start_time_abs = rsi->start_time_abs;
661 rci->end_time = rsi->start_time;
662 rci->selected = FALSE;
663 rci->frame_index = 0;
664 rci->drop_by_jitter_buff = 0;
666 rci->wrong_timestamp = 0;
667 rci->max_frame_index = 0;
668 rci->samples = g_array_new (FALSE, FALSE, sizeof(sample_t));
669 rci->check_bt = NULL;
670 rci->separator = NULL;
671 rci->draw_area = NULL;
672 #if GTK_CHECK_VERSION(2,22,0)
677 rci->h_scrollbar_adjustment = NULL;
678 rci->cursor_pixbuf = NULL;
679 rci->cursor_prev = 0;
680 rci->cursor_catch = FALSE;
681 rci->first_stream = rsi;
682 rci->num_packets = rsi->num_packets;
683 g_hash_table_insert(rtp_channels_hash, g_strdup(key_str->str), rci);
685 /* Add silence between the two streams if needed */
686 silence_frames = (gint32)( ((rsi->start_time - rci->end_time)/1000)*SAMPLE_RATE );
687 for (i = 0; i< silence_frames; i++) {
688 g_array_append_val(rci->samples, silence);
690 rci->num_packets += rsi->num_packets;
693 /* decode the RTP stream */
698 decoded_bytes_prev = 0;
700 arrive_time = start_time = 0;
701 arrive_time_prev = 0;
703 #ifdef DEBUG /* ?? */
710 #ifdef DEBUG /* ?? */
715 decoders_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, rtp_decoder_value_destroy);
717 /* we update the progress bar 100 times */
719 /* Update the progress bar when it gets to this value. */
720 progbar_nextstep = 0;
721 /* When we reach the value that triggers a progress bar update,
722 bump that value by this amount. */
723 progbar_quantum = total_packets/100;
727 rtp_packets_list = g_list_first(rsi->rtp_packets_list);
728 while (rtp_packets_list)
731 if (progbar_count >= progbar_nextstep) {
732 g_assert(total_packets > 0);
734 progbar_val = (gfloat) progbar_count / total_packets;
736 update_progress_bar(progbar_val);
738 progbar_nextstep += progbar_quantum;
742 rp = rtp_packets_list->data;
744 start_timestamp = rp->info->info_timestamp; /* defined start_timestmp to avoid overflow in timestamp. TODO: handle the timestamp correctly */
746 rtp_time_prev = start_rtp_time;
748 seq = rp->info->info_seq_num - 1;
751 decoded_bytes = decode_rtp_packet(rp, &out_buff, decoders_hash);
752 if (decoded_bytes == 0) {
753 seq = rp->info->info_seq_num;
756 rtp_time = (double)(rp->info->info_timestamp-start_timestamp)/SAMPLE_RATE - start_rtp_time;
758 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_use_rtp_timestamp))) {
759 arrive_time = rtp_time;
761 arrive_time = (double)rp->arrive_offset/1000 - start_time;
764 if (rp->info->info_seq_num != seq+1){
766 status = S_WRONG_SEQ;
768 seq = rp->info->info_seq_num;
770 diff = arrive_time - rtp_time;
772 delay = diff - prev_diff;
774 if (delay<0) delay = -delay;
776 if (diff<0) diff = -diff;
779 total_time = (double)rp->arrive_offset/1000;
780 printf("seq = %d arr = %f abs_diff = %f index = %d tim = %f ji=%d jb=%f\n",rp->info->info_seq_num,
781 total_time, diff, rci->samples->len, ((double)rci->samples->len/8000 - total_time)*1000, 0,
782 (mean_delay + 4*variation)*1000);
785 /* if the jitter buffer was exceeded */
786 if ( diff*1000 > jitter_buff ) {
788 printf("Packet drop by jitter buffer exceeded\n");
790 rci->drop_by_jitter_buff++;
791 status = S_DROP_BY_JITT;
793 /* if there was a silence period (more than two packetization period) resync the source */
794 if ( (rtp_time - rtp_time_prev) > pack_period*2 ){
796 printf("Resync...\n");
798 silence_frames = (gint32)((arrive_time - arrive_time_prev)*SAMPLE_RATE - decoded_bytes_prev/2);
800 /* Fix for bug 4119/5902: don't insert too many silence frames.
801 * XXX - is there a better thing to do here?
803 #define MAX_SILENCE_FRAMES 240000
804 if (silence_frames > MAX_SILENCE_FRAMES)
805 silence_frames = MAX_SILENCE_FRAMES;
807 for (i = 0; i< silence_frames; i++) {
808 silence.status = status;
809 g_array_append_val(rci->samples, silence);
811 /* only mark the first in the silence that has the previous problem (S_DROP_BY_JITT or S_WRONG_SEQ) */
815 decoded_bytes_prev = 0;
816 start_timestamp = rp->info->info_timestamp; /* defined start_timestamp to avoid overflow in timestamp. TODO: handle the timestamp correctly */
818 start_time = (double)rp->arrive_offset/1000;
822 /* Add silence if it is necessary */
823 silence_frames = (gint32)((rtp_time - rtp_time_prev)*SAMPLE_RATE - decoded_bytes_prev/2);
824 if (silence_frames != 0) {
825 rci->wrong_timestamp++;
826 status = S_WRONG_TIMESTAMP;
829 /* Fix for bug 4119/5902: don't insert too many silence frames.
830 * XXX - is there a better thing to do here?
832 if (silence_frames > MAX_SILENCE_FRAMES)
833 silence_frames = MAX_SILENCE_FRAMES;
835 for (i = 0; i< silence_frames; i++) {
836 silence.status = status;
837 g_array_append_val(rci->samples, silence);
839 /* only mark the first in the silence that has the previous problem (S_DROP_BY_JITT or S_WRONG_SEQ) */
844 if (silence_frames > 0) {
848 for (i = - silence_frames; i< (decoded_bytes/2); i++) {
849 sample.val = out_buff[i];
850 sample.status = status;
851 g_array_append_val(rci->samples, sample);
855 rtp_time_prev = rtp_time;
856 pack_period = (double)(decoded_bytes/2)/SAMPLE_RATE;
857 decoded_bytes_prev = decoded_bytes;
858 arrive_time_prev = arrive_time;
865 rtp_packets_list = g_list_next (rtp_packets_list);
868 rci->max_frame_index = rci->samples->len;
869 rci->end_time = rci->start_time + ((double)rci->samples->len/SAMPLE_RATE)*1000;
871 g_string_free(key_str, TRUE);
872 g_hash_table_destroy(decoders_hash);
875 /****************************************************************************/
877 h_scrollbar_changed(GtkWidget *widget _U_, gpointer user_data)
879 rtp_channel_info_t *rci = user_data;
880 rci->cursor_catch = TRUE;
884 static gboolean draw_cursors(gpointer data);
886 /****************************************************************************/
893 /* we should never be here if we are already in STOP */
894 g_assert(rtp_channels->stop == FALSE);
896 rtp_channels->stop = TRUE;
897 /* force a draw_cursor to stop it */
900 err = Pa_StopStream(pa_stream);
902 if( err != paNoError ) {
903 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
904 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
905 "Can not Stop Stream in PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
906 gtk_dialog_run (GTK_DIALOG (dialog));
907 gtk_widget_destroy (dialog);
911 err = Pa_CloseStream(pa_stream);
912 if( err != paNoError ) {
913 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
914 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
915 "Can not Close Stream in PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
916 gtk_dialog_run (GTK_DIALOG (dialog));
917 gtk_widget_destroy (dialog);
920 pa_stream = NULL; /* to catch errors better */
922 rtp_channels->start_index[0] = 0;
923 rtp_channels->start_index[1] = 0;
924 rtp_channels->end_index[0] = 0;
925 rtp_channels->end_index[1] = 0;
926 rtp_channels->max_frame_index = 0;
927 rtp_channels->frame_index = 0;
928 rtp_channels->pause = FALSE;
929 rtp_channels->pause_duration = 0;
930 rtp_channels->stop = TRUE;
931 rtp_channels->out_diff_time = 10000;
933 if (rtp_channels->rci[0]) rtp_channels->rci[0]->frame_index = 0;
934 if (rtp_channels->rci[1]) rtp_channels->rci[1]->frame_index = 0;
936 /* set the sensitive state of the buttons (decode, play, pause, stop) */
937 bt_state(TRUE, TRUE, FALSE, FALSE);
941 /****************************************************************************/
942 /* Draw a cursor in a channel graph
945 draw_channel_cursor(rtp_channel_info_t *rci, guint32 start_index)
949 #else /* PORTAUDIO_API_1 */
951 #endif /* PORTAUDIO_API_1 */
953 GtkAllocation widget_alloc;
959 idx = Pa_StreamTime( pa_stream ) - rtp_channels->pause_duration - rtp_channels->out_diff_time - start_index;
960 #else /* PORTAUDIO_API_1 */
961 idx = ((guint32)(SAMPLE_RATE) * (Pa_GetStreamTime(pa_stream)-rtp_channels->pa_start_time))- rtp_channels->pause_duration - rtp_channels->out_diff_time - start_index;
962 #endif /* PORTAUDIO_API_1 */
965 /* If we finished playing both channels, then stop them */
966 if ( (rtp_channels && (!rtp_channels->stop) && (!rtp_channels->pause)) && (idx > rtp_channels->max_frame_index) ) {
971 /* If only this channel finished, then return */
972 if (idx > rci->max_frame_index) {
976 gtk_widget_get_allocation(rci->draw_area, &widget_alloc);
977 /* draw the previous saved pixbuf line */
978 if (rci->cursor_pixbuf && (rci->cursor_prev>=0)) {
980 #if GTK_CHECK_VERSION(2,22,0)
981 cr = cairo_create (rci->surface);
983 cr = gdk_cairo_create (rci->pixmap);
985 gdk_cairo_set_source_pixbuf (cr, rci->cursor_pixbuf, 0, 0);
986 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
987 cairo_rectangle (cr, rci->cursor_prev/MULT, 0, -1, -1);
990 #if GTK_CHECK_VERSION(2,22,0)
991 cairo_set_source_surface (cr, rci->surface, idx/MULT, 0);
993 gdk_cairo_set_source_pixmap (cr, rci->pixmap,idx/MULT, 0);
995 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
996 cairo_rectangle (cr, rci->cursor_prev/MULT, 0, 1, widget_alloc.height-HEIGHT_TIME_LABEL);
1000 g_object_unref(rci->cursor_pixbuf);
1001 rci->cursor_pixbuf = NULL;
1004 if (idx>0 && (rci->cursor_prev>=0)) {
1005 #if GTK_CHECK_VERSION(2,22,0)
1006 rci->cursor_pixbuf = gdk_pixbuf_get_from_surface (rci->surface,0, 0, 1, widget_alloc.height-HEIGHT_TIME_LABEL);
1007 cr = cairo_create (rci->surface);
1009 rci->cursor_pixbuf = gdk_pixbuf_get_from_drawable(NULL, rci->pixmap, NULL, (int) (idx/MULT), 0, 0, 0, 1, widget_alloc.height-HEIGHT_TIME_LABEL);
1010 cr = gdk_cairo_create (rci->pixmap);
1012 cairo_set_line_width (cr, 1.0);
1013 cairo_move_to(cr, idx/MULT, 0);
1014 cairo_line_to(cr, idx/MULT, widget_alloc.height-HEIGHT_TIME_LABEL);
1018 cr = gdk_cairo_create (gtk_widget_get_window(rci->draw_area));
1019 #if GTK_CHECK_VERSION(2,22,0)
1020 cairo_set_source_surface (cr, rci->surface, idx/MULT, 0);
1022 gdk_cairo_set_source_pixmap (cr, rci->pixmap, idx/MULT, 0);
1024 cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT);
1025 cairo_rectangle (cr, idx/MULT, 0, 1, widget_alloc.height-HEIGHT_TIME_LABEL);
1030 /* Disconnect the scroll bar "value" signal to not be called */
1031 g_signal_handlers_disconnect_by_func(rci->h_scrollbar_adjustment, h_scrollbar_changed, rci);
1033 /* Move the horizontal scroll bar */
1035 if ( (rci->cursor_prev/MULT < (rci->h_scrollbar_adjustment->value+rci->h_scrollbar_adjustment->page_increment)) &&
1036 (idx/MULT >= (rci->h_scrollbar_adjustment->value+rci->h_scrollbar_adjustment->page_increment)) ){
1037 for (i=1; i<10; i++) {
1038 rci->h_scrollbar_adjustment->value += rci->h_scrollbar_adjustment->page_size/10;
1039 gtk_adjustment_value_changed(rci->h_scrollbar_adjustment);
1043 if (!rci->cursor_catch) {
1044 if (idx/MULT < gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment)/2) {
1045 gtk_adjustment_set_value(rci->h_scrollbar_adjustment, gtk_adjustment_get_lower(rci->h_scrollbar_adjustment));
1046 } else if (idx/MULT > (gtk_adjustment_get_upper(rci->h_scrollbar_adjustment) - gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment)/2)) {
1047 gtk_adjustment_set_value(rci->h_scrollbar_adjustment, gtk_adjustment_get_upper(rci->h_scrollbar_adjustment) - gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment));
1049 gtk_adjustment_set_value(rci->h_scrollbar_adjustment, idx/MULT - gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment)/2);
1052 gtk_adjustment_value_changed(rci->h_scrollbar_adjustment);
1053 } else if ( (rci->cursor_prev/MULT < gtk_adjustment_get_value(rci->h_scrollbar_adjustment)+gtk_adjustment_get_page_increment(rci->h_scrollbar_adjustment)) &&
1054 (idx/MULT >= gtk_adjustment_get_value(rci->h_scrollbar_adjustment) + gtk_adjustment_get_page_increment(rci->h_scrollbar_adjustment)) ){
1055 rci->cursor_catch = FALSE;
1056 for (i=1; i<10; i++) {
1057 gtk_adjustment_set_value(rci->h_scrollbar_adjustment, MIN(gtk_adjustment_get_upper(rci->h_scrollbar_adjustment)-gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment), gtk_adjustment_get_value(rci->h_scrollbar_adjustment) + gtk_adjustment_get_page_size(rci->h_scrollbar_adjustment)/20));
1058 gtk_adjustment_value_changed(rci->h_scrollbar_adjustment);
1062 /* Connect back the "value" scroll signal */
1063 g_signal_connect(rci->h_scrollbar_adjustment, "value_changed", G_CALLBACK(h_scrollbar_changed), rci);
1066 if (idx/MULT < rci->h_scrollbar_adjustment->page_increment) {
1067 rci->h_scrollbar_adjustment->value = rci->h_scrollbar_adjustment->lower;
1068 } else if (idx/MULT > (rci->h_scrollbar_adjustment->upper - rci->h_scrollbar_adjustment->page_size + rci->h_scrollbar_adjustment->page_increment)) {
1069 rci->h_scrollbar_adjustment->value = rci->h_scrollbar_adjustment->upper - rci->h_scrollbar_adjustment->page_size;
1071 if ( (idx/MULT < rci->h_scrollbar_adjustment->value) || (idx/MULT > (rci->h_scrollbar_adjustment->value+rci->h_scrollbar_adjustment->page_increment)) ){
1072 rci->h_scrollbar_adjustment->value = idx/MULT;
1078 if (idx/MULT < rci->h_scrollbar_adjustment->page_size/2) {
1079 rci->h_scrollbar_adjustment->value = rci->h_scrollbar_adjustment->lower;
1080 } else if (idx/MULT > (rci->h_scrollbar_adjustment->upper - rci->h_scrollbar_adjustment->page_size/2)) {
1081 rci->h_scrollbar_adjustment->value = rci->h_scrollbar_adjustment->upper - rci->h_scrollbar_adjustment->page_size;
1083 rci->h_scrollbar_adjustment->value = idx/MULT - rci->h_scrollbar_adjustment->page_size/2;
1088 gtk_adjustment_value_changed(rci->h_scrollbar_adjustment);
1090 rci->cursor_prev = idx;
1093 /****************************************************************************/
1094 /* Move and draw the cursor in the graph
1097 draw_cursors(gpointer data _U_)
1099 if (!rtp_channels) return FALSE;
1101 /* Draw and move each of the two channels */
1102 draw_channel_cursor(rtp_channels->rci[0], rtp_channels->start_index[0]);
1103 draw_channel_cursor(rtp_channels->rci[1], rtp_channels->start_index[1]);
1105 if ((rtp_channels->stop) || (rtp_channels->pause)) return FALSE;
1110 /****************************************************************************/
1112 init_rtp_channels_vals(void)
1114 rtp_play_channels_t *rpci = rtp_channels;
1116 /* if we only have one channel to play, we just use the info from that channel */
1117 if (rpci->rci[0] == NULL) {
1118 rpci->max_frame_index = rpci->rci[1]->max_frame_index;
1119 rpci->start_index[0] = rpci->max_frame_index;
1120 rpci->start_index[1] = 0;
1121 rpci->end_index[0] = rpci->max_frame_index;
1122 rpci->end_index[1] = rpci->max_frame_index;
1123 } else if (rpci->rci[1] == NULL) {
1124 rpci->max_frame_index = rpci->rci[0]->max_frame_index;
1125 rpci->start_index[1] = rpci->max_frame_index;
1126 rpci->start_index[0] = 0;
1127 rpci->end_index[0] = rpci->max_frame_index;
1128 rpci->end_index[1] = rpci->max_frame_index;
1130 /* if the two channels are to be played, then we need to sync both based on the start/end time of each one */
1132 rpci->max_frame_index = (guint32)(SAMPLE_RATE/1000) * (guint32)(MAX(rpci->rci[0]->end_time, rpci->rci[1]->end_time) -
1133 (guint32)MIN(rpci->rci[0]->start_time, rpci->rci[1]->start_time));
1135 if (rpci->rci[0]->start_time < rpci->rci[1]->start_time) {
1136 rpci->start_index[0] = 0;
1137 rpci->start_index[1] = (guint32)(SAMPLE_RATE/1000) * (guint32)(rpci->rci[1]->start_time - rpci->rci[0]->start_time);
1139 rpci->start_index[1] = 0;
1140 rpci->start_index[0] = (guint32)(SAMPLE_RATE/1000) * (guint32)(rpci->rci[0]->start_time - rpci->rci[1]->start_time);
1143 if (rpci->rci[0]->end_time < rpci->rci[1]->end_time) {
1144 rpci->end_index[0] = rpci->max_frame_index - ((guint32)(SAMPLE_RATE/1000) * (guint32)(rpci->rci[1]->end_time - rpci->rci[0]->end_time));
1145 rpci->end_index[1] = rpci->max_frame_index;
1147 rpci->end_index[1] = rpci->max_frame_index - ((guint32)(SAMPLE_RATE/1000) * (guint32)(rpci->rci[0]->end_time - rpci->rci[1]->end_time));
1148 rpci->end_index[0] = rpci->max_frame_index;
1154 /****************************************************************************/
1155 /* This routine will be called by the PortAudio engine when audio is needed.
1156 * It may called at interrupt level on some machines so don't do anything
1157 * that could mess up the system like calling malloc() or free().
1161 static int paCallback( void *inputBuffer, void *outputBuffer,
1162 unsigned long framesPerBuffer,
1163 PaTimestamp outTime, void *userData)
1165 #else /* PORTAUDIO_API_1 */
1166 static int paCallback( const void *inputBuffer, void *outputBuffer,
1167 unsigned long framesPerBuffer,
1168 const PaStreamCallbackTimeInfo* outTime,
1169 PaStreamCallbackFlags statusFlags _U_,
1172 #endif /* PORTAUDIO_API_1 */
1173 rtp_play_channels_t *rpci = (rtp_play_channels_t *)userData;
1174 SAMPLE *wptr = (SAMPLE*)outputBuffer;
1178 unsigned int framesLeft;
1181 /* if it is pasued, we keep the stream running but with silence only */
1182 if (rtp_channels->pause) {
1183 for(i=0; i<framesPerBuffer; i++ ) {
1187 rtp_channels->pause_duration += framesPerBuffer;
1192 rpci->out_diff_time = outTime - Pa_StreamTime(pa_stream) ;
1193 #else /* PORTAUDIO_API_1 */
1194 rpci->out_diff_time = (guint32)(SAMPLE_RATE) * (outTime->outputBufferDacTime - Pa_GetStreamTime(pa_stream)) ;
1195 #endif /* PORTAUDIO_API_1 */
1198 /* set the values if this is the first time */
1199 if (rpci->max_frame_index == 0) {
1200 init_rtp_channels_vals();
1204 framesLeft = rpci->max_frame_index - rpci->frame_index;
1206 (void) inputBuffer; /* Prevent unused variable warnings. */
1209 if( framesLeft < framesPerBuffer )
1211 framesToPlay = framesLeft;
1216 framesToPlay = framesPerBuffer;
1220 for( i=0; i<(unsigned int)framesToPlay; i++ )
1222 if (rpci->rci[0] && ( (rpci->frame_index >= rpci->start_index[0]) && (rpci->frame_index <= rpci->end_index[0]) )) {
1223 sample = g_array_index(rpci->rci[0]->samples, sample_t, rpci->rci[0]->frame_index++);
1224 *wptr++ = sample.val;
1229 if (rpci->rci[1] && ( (rpci->frame_index >= rpci->start_index[1]) && (rpci->frame_index <= rpci->end_index[1]) )) {
1230 sample = g_array_index(rpci->rci[1]->samples, sample_t, rpci->rci[1]->frame_index++);
1231 *wptr++ = sample.val;
1236 for( ; i<framesPerBuffer; i++ )
1241 rpci->frame_index += framesToPlay;
1246 /****************************************************************************/
1248 on_bt_check_clicked(GtkButton *button _U_, gpointer user_data)
1250 rtp_channel_info_t *rci = user_data;
1252 if (rci->selected) {
1253 if (rtp_channels->rci[0] == rci) {
1254 rtp_channels->rci[0] = NULL;
1255 rtp_channels->channel = 0;
1257 rtp_channels->rci[1] = NULL;
1258 rtp_channels->channel = 1;
1261 /* if there are already both channels selected, unselect the old one */
1262 if (rtp_channels->rci[rtp_channels->channel]) {
1263 /* we disconnect the signal temporarly to avoid been called back */
1264 g_signal_handlers_disconnect_by_func(rtp_channels->rci[rtp_channels->channel]->check_bt, on_bt_check_clicked, rtp_channels->rci[rtp_channels->channel]);
1265 gtk_toggle_button_set_active((GtkToggleButton *)rtp_channels->rci[rtp_channels->channel]->check_bt, FALSE);
1266 g_signal_connect(rtp_channels->rci[rtp_channels->channel]->check_bt, "clicked", G_CALLBACK(on_bt_check_clicked), rtp_channels->rci[rtp_channels->channel]);
1267 rtp_channels->rci[rtp_channels->channel]->selected = FALSE;
1270 rtp_channels->rci[rtp_channels->channel] = rci;
1271 rtp_channels->channel = !(rtp_channels->channel);
1274 rci->selected = !(rci->selected);
1276 /* set the sensitive state of the buttons (decode, play, pause, stop) */
1277 bt_state(TRUE, TRUE, FALSE, FALSE);
1280 /****************************************************************************/
1281 static void channel_draw(rtp_channel_info_t* rci)
1287 PangoLayout *small_layout;
1288 guint32 label_width, label_height;
1289 char label_string[MAX_TIME_LABEL];
1291 guint32 progbar_nextstep;
1292 int progbar_quantum;
1295 GdkColor red_color = {0, 65535, 0, 0};
1296 GdkColor amber_color = {0, 65535, 49152, 0};
1297 GdkColor white_color = {0, 65535, 65535, 65535};
1298 GdkColor black_color = {0, 0, 0, 0};
1300 GdkColor *draw_color_p;
1302 struct tm *timestamp;
1303 GtkAllocation widget_alloc;
1306 #if GTK_CHECK_VERSION(2,22,0)
1307 gtk_widget_get_allocation(rci->draw_area, &widget_alloc);
1308 /* Clear out old plot */
1309 cr = cairo_create (rci->surface);
1310 gdk_cairo_set_source_color (cr, &rci->bg_color[1+rci->call_num%MAX_NUM_COL_CONV]);
1311 cairo_rectangle (cr, 0, 0, widget_alloc.width,widget_alloc.height);
1316 small_layout = gtk_widget_create_pango_layout(rci->draw_area, NULL);
1317 pango_layout_set_font_description(small_layout, pango_font_description_from_string("Helvetica,Sans,Bold 7"));
1319 /* calculated the pixel offset to display integer seconds */
1320 offset = ((double)rci->start_time/1000 - floor((double)rci->start_time/1000))*SAMPLE_RATE/MULT;
1322 cr = cairo_create (rci->surface);
1323 cairo_set_line_width (cr, 1.0);
1324 cairo_move_to(cr, 0, widget_alloc.height-HEIGHT_TIME_LABEL+0.5);
1325 cairo_line_to(cr, widget_alloc.width, widget_alloc.height-HEIGHT_TIME_LABEL+0.5);
1330 imax = MIN(widget_alloc.width,(gint)(rci->samples->len/MULT));
1332 /* we update the progress bar 100 times */
1334 /* Update the progress bar when it gets to this value. */
1335 progbar_nextstep = 0;
1336 /* When we reach the value that triggers a progress bar update,
1337 bump that value by this amount. */
1338 progbar_quantum = imax/100;
1340 for (i=0; i< imax; i++) {
1346 if (progbar_count >= progbar_nextstep) {
1347 g_assert(total_frames > 0);
1349 progbar_val = (gfloat) i / imax;
1351 update_progress_bar(progbar_val);
1353 progbar_nextstep += progbar_quantum;
1356 for (j=0; j<MULT; j++) {
1357 sample = g_array_index(rci->samples, sample_t, i*MULT+j);
1358 max = MAX(max, sample.val);
1359 min = MIN(min, sample.val);
1360 if (sample.status == S_DROP_BY_JITT) status = S_DROP_BY_JITT;
1361 if (sample.status == S_WRONG_TIMESTAMP) status = S_WRONG_TIMESTAMP;
1362 if (sample.status == S_SILENCE) status = S_SILENCE;
1365 /* Set the line color, default is black */
1366 if (status == S_DROP_BY_JITT) {
1367 draw_color_p = &red_color;
1368 } else if (status == S_WRONG_TIMESTAMP) {
1369 draw_color_p = &amber_color;
1370 } else if (status == S_SILENCE) {
1371 draw_color_p = &white_color;
1373 draw_color_p = &black_color;
1376 /* if silence added by Wireshark, graphically show it with letter to indicate why */
1377 if ((status == S_DROP_BY_JITT) || (status == S_WRONG_TIMESTAMP) || (status == S_SILENCE)) {
1378 cr = cairo_create (rci->surface);
1379 cairo_set_line_width (cr, 1.0);
1380 gdk_cairo_set_source_color (cr, draw_color_p);
1381 cairo_move_to(cr, i+0.5, 0);
1382 cairo_line_to(cr, i+0.5, (widget_alloc.height-HEIGHT_TIME_LABEL)-1);
1387 if (status == S_DROP_BY_JITT) g_snprintf(label_string, MAX_TIME_LABEL,"D");
1388 if (status == S_WRONG_TIMESTAMP) g_snprintf(label_string, MAX_TIME_LABEL, "W");
1389 if (status == S_SILENCE) g_snprintf(label_string, MAX_TIME_LABEL, "S");
1391 pango_layout_set_text(small_layout, label_string, -1);
1392 pango_layout_get_pixel_size(small_layout, &label_width, &label_height);
1393 cr = cairo_create (rci->surface);
1394 gdk_cairo_set_source_color (cr, draw_color_p);
1395 cairo_move_to (cr, i, 0);
1396 pango_cairo_show_layout (cr, small_layout);
1400 /* Draw a graphical representation of the sample */
1401 cr = cairo_create (rci->surface);
1402 cairo_set_line_width (cr, 1.0);
1403 gdk_cairo_set_source_color (cr, draw_color_p);
1404 cairo_move_to(cr, i+0.5, ( (0x7FFF+min) * (widget_alloc.height-HEIGHT_TIME_LABEL))/0xFFFF);
1405 cairo_line_to(cr, i+0.5, ( (0x7FFF+max) * (widget_alloc.height-HEIGHT_TIME_LABEL))/0xFFFF);
1410 /* Draw the x-axis (seconds since beginning of packet flow for this call) */
1412 /* Draw tick mark and put a number for each whole second */
1413 if ( !((i*MULT)%(SAMPLE_RATE)) ) {
1414 cr = cairo_create (rci->surface);
1415 cairo_set_line_width (cr, 1.0);
1416 cairo_move_to(cr, i - offset+0.5, widget_alloc.height-HEIGHT_TIME_LABEL);
1417 cairo_line_to(cr, i+0.5, widget_alloc.height-HEIGHT_TIME_LABEL+4);
1422 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_view_as_time_of_day))) {
1423 seconds = rci->start_time_abs.secs + i * MULT / SAMPLE_RATE;
1424 timestamp = localtime(&seconds);
1425 g_snprintf(label_string, MAX_TIME_LABEL, "%02d:%02d:%02d", timestamp->tm_hour, timestamp->tm_min, timestamp->tm_sec);
1427 g_snprintf(label_string, MAX_TIME_LABEL, "%.0f s", floor(rci->start_time/1000) + i*MULT/SAMPLE_RATE);
1430 pango_layout_set_text(small_layout, label_string, -1);
1431 pango_layout_get_pixel_size(small_layout, &label_width, &label_height);
1432 cr = cairo_create (rci->surface);
1433 cairo_move_to (cr, i - offset - label_width/2, widget_alloc.height - label_height);
1434 pango_cairo_show_layout (cr, small_layout);
1439 /* Draw only a tick mark for half second intervals */
1440 } else if ( !((i*MULT)%(SAMPLE_RATE/2)) ) {
1441 cr = cairo_create (rci->surface);
1442 cairo_set_line_width (cr, 1.0);
1443 cairo_move_to(cr,i - offset+0.5, widget_alloc.height-HEIGHT_TIME_LABEL);
1444 cairo_line_to(cr, (i - offset)+0.5, widget_alloc.height-HEIGHT_TIME_LABEL+2);
1452 g_object_unref(G_OBJECT(small_layout));
1454 if (GDK_IS_DRAWABLE(rci->pixmap)) {
1455 gtk_widget_get_allocation(rci->draw_area, &widget_alloc);
1456 /* Clear out old plot */
1457 cr = gdk_cairo_create (rci->pixmap);
1458 gdk_cairo_set_source_color (cr, &rci->bg_color[1+rci->call_num%MAX_NUM_COL_CONV]);
1459 cairo_rectangle (cr, 0, 0, widget_alloc.width,widget_alloc.height);
1464 small_layout = gtk_widget_create_pango_layout(rci->draw_area, NULL);
1465 pango_layout_set_font_description(small_layout, pango_font_description_from_string("Helvetica,Sans,Bold 7"));
1467 /* calculated the pixel offset to display integer seconds */
1468 offset = ((double)rci->start_time/1000 - floor((double)rci->start_time/1000))*SAMPLE_RATE/MULT;
1470 cr = gdk_cairo_create (rci->pixmap);
1471 cairo_set_line_width (cr, 1.0);
1472 cairo_move_to(cr, 0, widget_alloc.height-HEIGHT_TIME_LABEL+0.5);
1473 cairo_line_to(cr, widget_alloc.width, widget_alloc.height-HEIGHT_TIME_LABEL+0.5);
1478 imax = MIN(widget_alloc.width,(gint)(rci->samples->len/MULT));
1480 /* we update the progress bar 100 times */
1482 /* Update the progress bar when it gets to this value. */
1483 progbar_nextstep = 0;
1484 /* When we reach the value that triggers a progress bar update,
1485 bump that value by this amount. */
1486 progbar_quantum = imax/100;
1488 for (i=0; i< imax; i++) {
1494 if (progbar_count >= progbar_nextstep) {
1495 g_assert(total_frames > 0);
1497 progbar_val = (gfloat) i / imax;
1499 update_progress_bar(progbar_val);
1501 progbar_nextstep += progbar_quantum;
1504 for (j=0; j<MULT; j++) {
1505 sample = g_array_index(rci->samples, sample_t, i*MULT+j);
1506 max = MAX(max, sample.val);
1507 min = MIN(min, sample.val);
1508 if (sample.status == S_DROP_BY_JITT) status = S_DROP_BY_JITT;
1509 if (sample.status == S_WRONG_TIMESTAMP) status = S_WRONG_TIMESTAMP;
1510 if (sample.status == S_SILENCE) status = S_SILENCE;
1513 /* Set the line color, default is black */
1514 if (status == S_DROP_BY_JITT) {
1515 draw_color_p = &red_color;
1516 } else if (status == S_WRONG_TIMESTAMP) {
1517 draw_color_p = &amber_color;
1518 } else if (status == S_SILENCE) {
1519 draw_color_p = &white_color;
1521 draw_color_p = &black_color;
1524 /* if silence added by Wireshark, graphically show it with letter to indicate why */
1525 if ((status == S_DROP_BY_JITT) || (status == S_WRONG_TIMESTAMP) || (status == S_SILENCE)) {
1526 cr = gdk_cairo_create (rci->pixmap);
1527 cairo_set_line_width (cr, 1.0);
1528 gdk_cairo_set_source_color (cr, draw_color_p);
1529 cairo_move_to(cr, i+0.5, 0);
1530 cairo_line_to(cr, i+0.5, (widget_alloc.height-HEIGHT_TIME_LABEL)-1);
1535 if (status == S_DROP_BY_JITT) g_snprintf(label_string, MAX_TIME_LABEL,"D");
1536 if (status == S_WRONG_TIMESTAMP) g_snprintf(label_string, MAX_TIME_LABEL, "W");
1537 if (status == S_SILENCE) g_snprintf(label_string, MAX_TIME_LABEL, "S");
1539 pango_layout_set_text(small_layout, label_string, -1);
1540 pango_layout_get_pixel_size(small_layout, &label_width, &label_height);
1541 cr = gdk_cairo_create (rci->pixmap);
1542 gdk_cairo_set_source_color (cr, draw_color_p);
1543 cairo_move_to (cr, i, 0);
1544 pango_cairo_show_layout (cr, small_layout);
1548 /* Draw a graphical representation of the sample */
1549 cr = gdk_cairo_create (rci->pixmap);
1550 cairo_set_line_width (cr, 1.0);
1551 gdk_cairo_set_source_color (cr, draw_color_p);
1552 cairo_move_to(cr, i+0.5, ( (0x7FFF+min) * (widget_alloc.height-HEIGHT_TIME_LABEL))/0xFFFF);
1553 cairo_line_to(cr, i+0.5, ( (0x7FFF+max) * (widget_alloc.height-HEIGHT_TIME_LABEL))/0xFFFF);
1558 /* Draw the x-axis (seconds since beginning of packet flow for this call) */
1560 /* Draw tick mark and put a number for each whole second */
1561 if ( !((i*MULT)%(SAMPLE_RATE)) ) {
1562 cr = gdk_cairo_create (rci->pixmap);
1563 cairo_set_line_width (cr, 1.0);
1564 cairo_move_to(cr, i - offset+0.5, widget_alloc.height-HEIGHT_TIME_LABEL);
1565 cairo_line_to(cr, i+0.5, widget_alloc.height-HEIGHT_TIME_LABEL+4);
1570 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_view_as_time_of_day))) {
1571 seconds = rci->start_time_abs.secs + i * MULT / SAMPLE_RATE;
1572 timestamp = localtime(&seconds);
1573 g_snprintf(label_string, MAX_TIME_LABEL, "%02d:%02d:%02d", timestamp->tm_hour, timestamp->tm_min, timestamp->tm_sec);
1575 g_snprintf(label_string, MAX_TIME_LABEL, "%.0f s", floor(rci->start_time/1000) + i*MULT/SAMPLE_RATE);
1578 pango_layout_set_text(small_layout, label_string, -1);
1579 pango_layout_get_pixel_size(small_layout, &label_width, &label_height);
1580 cr = gdk_cairo_create (rci->pixmap);
1581 cairo_move_to (cr, i - offset - label_width/2, widget_alloc.height - label_height);
1582 pango_cairo_show_layout (cr, small_layout);
1587 /* Draw only a tick mark for half second intervals */
1588 } else if ( !((i*MULT)%(SAMPLE_RATE/2)) ) {
1589 cr = gdk_cairo_create (rci->pixmap);
1590 cairo_set_line_width (cr, 1.0);
1591 cairo_move_to(cr,i - offset+0.5, widget_alloc.height-HEIGHT_TIME_LABEL);
1592 cairo_line_to(cr, (i - offset)+0.5, widget_alloc.height-HEIGHT_TIME_LABEL+2);
1600 g_object_unref(G_OBJECT(small_layout));
1605 /****************************************************************************/
1606 static gboolean expose_event_channels(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
1608 rtp_channel_info_t *rci = user_data;
1611 if (gtk_widget_is_drawable(widget)){
1612 cr = gdk_cairo_create (gtk_widget_get_window(widget));
1613 #if GTK_CHECK_VERSION(2,22,0)
1614 cairo_set_source_surface (cr, rci->surface, 0, 0);
1616 gdk_cairo_set_source_pixmap (cr, rci->pixmap, event->area.x, event->area.y);
1618 cairo_rectangle (cr, event->area.x,event->area.y, event->area.width, event->area.height);
1627 /****************************************************************************/
1629 configure_event_channels(GtkWidget *widget, GdkEventConfigure *event _U_, gpointer user_data)
1631 rtp_channel_info_t *rci = user_data;
1633 GtkAllocation widget_alloc;
1636 /* the first color is blue to highlight the selected item
1637 * the other collors are the same as in the Voip Graph analysys
1638 * to match the same calls
1640 static GdkColor col[MAX_NUM_COL_CONV+1] = {
1641 {0, 0x00FF, 0x00FF, 0xFFFF},
1642 {0, 0x90FF, 0xEEFF, 0x90FF},
1643 {0, 0xFFFF, 0xA0FF, 0x7AFF},
1644 {0, 0xFFFF, 0xB6FF, 0xC1FF},
1645 {0, 0xFAFF, 0xFAFF, 0xD2FF},
1646 {0, 0xFFFF, 0xFFFF, 0x33FF},
1647 {0, 0x66FF, 0xCDFF, 0xAAFF},
1648 {0, 0xE0FF, 0xFFFF, 0xFFFF},
1649 {0, 0xB0FF, 0xC4FF, 0xDEFF},
1650 {0, 0x87FF, 0xCEFF, 0xFAFF},
1651 {0, 0xD3FF, 0xD3FF, 0xD3FF}
1654 #if GTK_CHECK_VERSION(2,22,0)
1656 cairo_surface_destroy (rci->surface);
1659 gtk_widget_get_allocation(widget, &widget_alloc);
1660 rci->surface = gdk_window_create_similar_surface (gtk_widget_get_window(widget),
1661 CAIRO_CONTENT_COLOR,
1663 widget_alloc.height);
1665 cr = cairo_create (rci->surface);
1666 cairo_set_source_rgb (cr, 1, 1, 1);
1667 cairo_rectangle (cr, 0, 0, widget_alloc.width,widget_alloc.height);
1672 g_object_unref(rci->pixmap);
1675 gtk_widget_get_allocation(widget, &widget_alloc);
1676 rci->pixmap = gdk_pixmap_new(gtk_widget_get_window(widget),
1678 widget_alloc.height,
1680 if ( GDK_IS_DRAWABLE(rci->pixmap) ){
1681 cr = gdk_cairo_create (rci->pixmap);
1682 cairo_set_source_rgb (cr, 1, 1, 1);
1683 cairo_rectangle (cr, 0, 0, widget_alloc.width,widget_alloc.height);
1689 /* create gc's for the background color of each channel */
1690 for (i=0; i<MAX_NUM_COL_CONV+1; i++){
1691 rci->bg_color[i].pixel=col[i].pixel;
1692 rci->bg_color[i].red=col[i].red;
1693 rci->bg_color[i].green=col[i].green;
1694 rci->bg_color[i].blue=col[i].blue;
1703 /****************************************************************************/
1705 button_press_event_channel(GtkWidget *widget _U_, GdkEventButton *event _U_, gpointer user_data)
1707 rtp_channel_info_t *rci = user_data;
1711 if (!rci->selected) {
1713 /* only select a new channels if we are in STOP */
1714 if (!rtp_channels->stop) return 0;
1716 /* if there are already both channels selected, unselect the old one */
1717 if (rtp_channels->rci[rtp_channels->channel]) {
1718 /* we disconnect the signal temporarly to avoid been called back */
1719 g_signal_handlers_disconnect_by_func(rtp_channels->rci[rtp_channels->channel]->check_bt, on_bt_check_clicked, rtp_channels->rci[rtp_channels->channel]);
1720 gtk_toggle_button_set_active((GtkToggleButton *) rtp_channels->rci[rtp_channels->channel]->check_bt, FALSE);
1721 g_signal_connect(rtp_channels->rci[rtp_channels->channel]->check_bt, "clicked", G_CALLBACK(on_bt_check_clicked), rtp_channels->rci[rtp_channels->channel]);
1722 rtp_channels->rci[rtp_channels->channel]->selected = FALSE;
1725 /* we disconnect the signal temporarly to avoid been called back */
1726 g_signal_handlers_disconnect_by_func(rci->check_bt, on_bt_check_clicked, rci);
1727 gtk_toggle_button_set_active((GtkToggleButton *) rci->check_bt, TRUE);
1728 g_signal_connect(rci->check_bt, "clicked", G_CALLBACK(on_bt_check_clicked), rci);
1730 rtp_channels->rci[rtp_channels->channel] = rci;
1731 rtp_channels->channel = !(rtp_channels->channel);
1732 rci->selected = TRUE;
1734 /* set the sensitive state of the buttons (decode, play, pause, stop) */
1735 bt_state(TRUE, TRUE, FALSE, FALSE);
1738 if (rci == rtp_channels->rci[0]) {
1744 rci->frame_index = (unsigned int) (event->x * MULT);
1746 prev_index = rtp_channels->frame_index;
1747 rtp_channels->frame_index = rtp_channels->start_index[this_channel] + rci->frame_index;
1748 rtp_channels->pause_duration += prev_index - rtp_channels->frame_index;
1752 /* change the index in the other channel if selected, according with the index position */
1753 if (rtp_channels->rci[!this_channel]) {
1754 init_rtp_channels_vals();
1756 if (rtp_channels->frame_index < rtp_channels->start_index[!this_channel]) {
1757 rtp_channels->rci[!this_channel]->frame_index = 0;
1758 } else if (rtp_channels->frame_index > rtp_channels->end_index[!this_channel]) {
1759 rtp_channels->rci[!this_channel]->frame_index = rtp_channels->rci[!this_channel]->max_frame_index;
1761 rtp_channels->rci[!this_channel]->frame_index = rtp_channels->frame_index - rtp_channels->start_index[!this_channel];
1764 init_rtp_channels_vals();
1767 rtp_channels->out_diff_time = 0;
1769 rci->cursor_catch = TRUE;
1771 /* redraw the cusrsor */
1777 /****************************************************************************/
1779 add_channel_to_window(gchar *key _U_ , rtp_channel_info_t *rci, guint *counter _U_ )
1781 GString *label = NULL;
1782 GtkWidget *viewport;
1785 /* create the channel draw area */
1786 rci->draw_area=gtk_drawing_area_new();
1788 rci->scroll_window=gtk_scrolled_window_new(NULL, NULL);
1790 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (rci->scroll_window), GTK_POLICY_ALWAYS, GTK_POLICY_NEVER);
1791 rci->h_scrollbar_adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(rci->scroll_window));
1794 gtk_widget_set_size_request(rci->draw_area, (gint)(rci->samples->len/MULT), CHANNEL_HEIGHT);
1797 viewport = gtk_viewport_new(rci->h_scrollbar_adjustment, gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(rci->scroll_window)));
1798 gtk_container_add(GTK_CONTAINER(viewport), rci->draw_area);
1799 gtk_container_add(GTK_CONTAINER(rci->scroll_window), viewport);
1800 gtk_viewport_set_shadow_type(GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);
1801 gtk_widget_add_events (rci->draw_area, GDK_BUTTON_PRESS_MASK);
1802 #if GTK_CHECK_VERSION(2,18,0)
1803 gtk_widget_set_can_focus(rci->draw_area, TRUE);
1805 GTK_WIDGET_SET_FLAGS(rci->draw_area, GTK_CAN_FOCUS);
1807 gtk_widget_grab_focus(rci->draw_area);
1809 gtk_box_pack_start(GTK_BOX (channels_vb), rci->scroll_window, FALSE, FALSE, 0);
1811 /* signals needed to handle backing pixmap */
1812 g_signal_connect(rci->draw_area, "expose_event", G_CALLBACK(expose_event_channels), rci);
1813 g_signal_connect(rci->draw_area, "configure_event", G_CALLBACK(configure_event_channels), rci);
1814 gtk_widget_add_events (rci->draw_area, GDK_BUTTON_PRESS_MASK);
1815 g_signal_connect(rci->draw_area, "button_press_event", G_CALLBACK(button_press_event_channel), rci);
1816 g_signal_connect(rci->h_scrollbar_adjustment, "value_changed", G_CALLBACK(h_scrollbar_changed), rci);
1819 label = g_string_new("");
1820 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(cb_use_rtp_timestamp))) {
1821 g_string_printf(label, "From %s:%d to %s:%d Duration:%.2f Out of Seq: %d(%.1f%%) Wrong Timestamp: %d(%.1f%%)",
1822 get_addr_name(&(rci->first_stream->src_addr)), rci->first_stream->src_port,
1823 get_addr_name(&(rci->first_stream->dest_addr)), rci->first_stream->dest_port,
1824 (double)rci->samples->len/SAMPLE_RATE,
1825 rci->out_of_seq, (double)rci->out_of_seq * 100 / (double)rci->num_packets,
1826 rci->wrong_timestamp, (double)rci->wrong_timestamp * 100 / (double)rci->num_packets);
1828 g_string_printf(label, "From %s:%d to %s:%d Duration:%.2f Drop by Jitter Buff:%d(%.1f%%) Out of Seq: %d(%.1f%%) Wrong Timestamp: %d(%.1f%%)",
1829 get_addr_name(&(rci->first_stream->src_addr)), rci->first_stream->src_port,
1830 get_addr_name(&(rci->first_stream->dest_addr)), rci->first_stream->dest_port,
1831 (double)rci->samples->len/SAMPLE_RATE,
1832 rci->drop_by_jitter_buff, (double)rci->drop_by_jitter_buff * 100 / (double)rci->num_packets,
1833 rci->out_of_seq, (double)rci->out_of_seq * 100 / (double)rci->num_packets,
1834 rci->wrong_timestamp, (double)rci->wrong_timestamp * 100 / (double)rci->num_packets);
1837 rci->check_bt = gtk_check_button_new_with_label(label->str);
1838 gtk_box_pack_start(GTK_BOX (channels_vb), rci->check_bt, FALSE, FALSE, 1);
1840 /* Create the Separator if it is not the last one */
1842 if (*counter < g_hash_table_size(rtp_channels_hash)) {
1843 rci->separator = gtk_hseparator_new();
1844 gtk_box_pack_start(GTK_BOX (channels_vb), rci->separator, FALSE, FALSE, 5);
1847 g_signal_connect(rci->check_bt, "clicked", G_CALLBACK(on_bt_check_clicked), rci);
1849 g_string_free(label, TRUE);
1852 /****************************************************************************/
1854 count_channel_frames(gchar *key _U_ , rtp_channel_info_t *rci, gpointer ptr _U_ )
1856 total_frames += rci->samples->len;
1859 /****************************************************************************/
1866 /* we should never be here if we are in PLAY and !PAUSE */
1867 g_assert(!rtp_channels->stop && !rtp_channels->pause);
1869 /* if we are in PAUSE change the state */
1870 if (rtp_channels->pause) {
1871 rtp_channels->pause = FALSE;
1872 /* set the sensitive state of the buttons (decode, play, pause, stop) */
1873 bt_state(FALSE, FALSE, TRUE, TRUE);
1875 /* if not PAUSE, then start to PLAY */
1878 err = Pa_OpenStream(
1880 paNoDevice, /* default input device */
1882 PA_SAMPLE_TYPE, /* 16 bit Integer input */
1884 Pa_GetDefaultOutputDeviceID(),
1885 NUM_CHANNELS, /* Stereo output */
1886 PA_SAMPLE_TYPE, /* 16 bit Integer output */
1888 SAMPLE_RATE, /* 8 kHz */
1890 0, /* number of buffers, if zero then use default minimum */
1891 paClipOff, /* we won't output out of range samples so don't bother clipping them */
1895 if( err != paNoError ) {
1896 const char *deviceName = "No Device";
1898 PaDeviceID device = Pa_GetDefaultOutputDeviceID();
1900 if (device != paNoDevice)
1902 const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo( device );
1904 deviceName = deviceInfo->name;
1906 deviceName = "(No device info)";
1909 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
1910 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
1911 "Got this info from PortAudio Library:\n"
1912 " Default deviceName: %s (%d)", deviceName, device);
1913 gtk_dialog_run (GTK_DIALOG (dialog));
1914 gtk_widget_destroy (dialog);
1916 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
1917 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
1918 "Can not Open Stream in PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
1919 gtk_dialog_run (GTK_DIALOG (dialog));
1920 gtk_widget_destroy (dialog);
1923 #else /* PORTAUDIO_API_1 */
1924 if (Pa_GetDefaultOutputDevice() != paNoDevice) {
1925 err = Pa_OpenDefaultStream(
1928 NUM_CHANNELS, /* Stereo output */
1929 PA_SAMPLE_TYPE, /* 16 bit Integer output */
1930 SAMPLE_RATE, /* 8 kHz */
1935 /* If the Default Host API doesn't even provide a device
1936 * we might as well go look for another.
1938 PaHostApiIndex host_api_count = Pa_GetHostApiCount();
1939 PaHostApiIndex default_host_api_index = Pa_GetDefaultHostApi();
1941 PaHostApiIndex host_api_index;
1942 const PaHostApiInfo *host_api_info;
1944 for (host_api_index=0; host_api_index<host_api_count; host_api_index++)
1946 /* Skip the default host API, that didn't work before */
1947 if (host_api_index == default_host_api_index)
1950 /* If we find a host API with a device, then take it. */
1951 host_api_info = Pa_GetHostApiInfo(host_api_index);
1952 if (host_api_info->deviceCount > 0)
1956 if (host_api_index<host_api_count)
1958 PaStreamParameters stream_parameters;
1959 stream_parameters.device = host_api_info->defaultOutputDevice;
1960 stream_parameters.channelCount = NUM_CHANNELS; /* Stereo output */
1961 stream_parameters.sampleFormat = PA_SAMPLE_TYPE; /* 16 bit Integer output */
1962 stream_parameters.suggestedLatency = 0;
1963 stream_parameters.hostApiSpecificStreamInfo = NULL;
1965 g_print("Trying Host API: %s\n", host_api_info->name);
1967 err = Pa_OpenStream(
1969 NULL, /* no input */
1971 SAMPLE_RATE, /* 8 kHz */
1973 paClipOff, /* we won't output out of range samples so don't bother clipping them */
1983 if( err != paNoError ) {
1984 PaHostApiIndex hostApi = Pa_GetDefaultHostApi();
1987 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
1988 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
1989 "Can not even get the default host API from PortAudio Library.\n Error: %s",
1990 Pa_GetErrorText( hostApi ));
1991 gtk_dialog_run (GTK_DIALOG (dialog));
1992 gtk_widget_destroy (dialog);
1996 const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo( hostApi );
2000 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2001 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2002 "Can not even get the host API info from PortAudio Library.");
2003 gtk_dialog_run (GTK_DIALOG (dialog));
2004 gtk_widget_destroy (dialog);
2008 const char *hostApiName = hostApiInfo->name;
2009 const char *deviceName = "No Device";
2011 PaDeviceIndex device = hostApiInfo->defaultOutputDevice;
2013 if (device != paNoDevice)
2015 const PaDeviceInfo* deviceInfo = Pa_GetDeviceInfo( device );
2017 deviceName = deviceInfo->name;
2019 deviceName = "(No device info)";
2022 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2023 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2024 "Got this info from PortAudio Library:\n"
2025 " Default hostApiName: %s\n"
2026 " Default deviceName: %s (%d)", hostApiName, deviceName, device);
2027 gtk_dialog_run (GTK_DIALOG (dialog));
2028 gtk_widget_destroy (dialog);
2032 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2033 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2034 "Can not Open Stream in PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
2035 gtk_dialog_run (GTK_DIALOG (dialog));
2036 gtk_widget_destroy (dialog);
2041 err = Pa_StartStream( pa_stream );
2042 if( err != paNoError ) {
2043 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2044 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2045 "Can not Start Stream in PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
2046 gtk_dialog_run (GTK_DIALOG (dialog));
2047 gtk_widget_destroy (dialog);
2050 #if !PORTAUDIO_API_1
2051 rtp_channels->pa_start_time = Pa_GetStreamTime(pa_stream);
2052 #endif /* PORTAUDIO_API_1 */
2054 rtp_channels->stop = FALSE;
2056 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2057 bt_state(FALSE, FALSE, TRUE, TRUE);
2060 /* Draw the cursor in the graph */
2061 g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, MULT*1000/SAMPLE_RATE, draw_cursors, NULL, NULL);
2065 /****************************************************************************/
2067 pause_channels(void)
2069 rtp_channels->pause = !(rtp_channels->pause);
2071 /* reactivate the cusrosr display if no in pause */
2072 if (!rtp_channels->pause) {
2073 /* Draw the cursor in the graph */
2074 g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, MULT*1000/SAMPLE_RATE, draw_cursors, NULL, NULL);
2077 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2078 bt_state(FALSE, TRUE, FALSE, TRUE);
2081 /****************************************************************************/
2083 reset_rtp_channels(void)
2085 rtp_channels->channel = 0;
2086 rtp_channels->rci[0] = NULL;
2087 rtp_channels->rci[1] = NULL;
2088 rtp_channels->start_index[0] = 0;
2089 rtp_channels->start_index[1] = 0;
2090 rtp_channels->end_index[0] = 0;
2091 rtp_channels->end_index[1] = 0;
2092 rtp_channels->max_frame_index = 0;
2093 rtp_channels->frame_index = 0;
2094 rtp_channels->pause = FALSE;
2095 rtp_channels->pause_duration = 0;
2096 rtp_channels->stop = TRUE;
2097 rtp_channels->out_diff_time = 10000;
2100 /****************************************************************************/
2102 remove_channel_to_window(gchar *key _U_ , rtp_channel_info_t *rci, gpointer ptr _U_ )
2104 #if GTK_CHECK_VERSION(2,22,0)
2106 cairo_surface_destroy (rci->surface);
2110 g_object_unref(rci->pixmap);
2112 gtk_widget_destroy(rci->draw_area);
2113 gtk_widget_destroy(rci->scroll_window);
2114 gtk_widget_destroy(rci->check_bt);
2116 gtk_widget_destroy(rci->separator);
2119 /****************************************************************************/
2121 reset_channels(void)
2124 if (rtp_channels_hash) {
2125 /* Remove the channels from the main window if there are there */
2126 g_hash_table_foreach( rtp_channels_hash, (GHFunc)remove_channel_to_window, NULL);
2129 /* destroy the rtp channels hash table */
2130 g_hash_table_destroy(rtp_channels_hash);
2131 rtp_channels_hash = NULL;
2135 reset_rtp_channels();
2139 /****************************************************************************/
2141 reset_rtp_player(void)
2143 /* Destroy the rtp channels */
2146 /* destroy the rtp streams hash table */
2147 if (rtp_streams_hash) {
2148 g_hash_table_destroy(rtp_streams_hash);
2149 rtp_streams_hash = NULL;
2152 /* destroy the rtp streams list */
2153 if (rtp_streams_list) {
2154 g_list_free (rtp_streams_list);
2155 rtp_streams_list = NULL;
2160 /****************************************************************************/
2162 decode_streams(void)
2164 guint statusbar_context;
2167 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2168 bt_state(FALSE, FALSE, FALSE, FALSE);
2172 progress_bar = gtk_progress_bar_new();
2173 gtk_widget_set_size_request(progress_bar, 100, -1);
2174 gtk_box_pack_start(GTK_BOX (stat_hbox), progress_bar, FALSE, FALSE, 2);
2175 gtk_widget_show(progress_bar);
2176 statusbar_context = gtk_statusbar_get_context_id((GtkStatusbar *) info_bar, "main");
2177 gtk_statusbar_push((GtkStatusbar *) info_bar, statusbar_context, " Decoding RTP packets...");
2179 #if !GTK_CHECK_VERSION(3,0,0)
2180 gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(info_bar), FALSE);
2182 /* reset the number of packet to be decoded, this is used for the progress bar */
2184 /* reset the Progress Bar count */
2187 /* Mark the RTP streams to be played using the selected VoipCalls. If voip_calls is NULL
2188 then this was called from "RTP Analysis" so mark all strams */
2189 if (rtp_streams_hash) {
2191 g_hash_table_foreach( rtp_streams_hash, (GHFunc)mark_rtp_stream_to_play, NULL);
2193 g_hash_table_foreach( rtp_streams_hash, (GHFunc)mark_all_rtp_stream_to_play, NULL);
2196 /* Decode the RTP streams and add them to the RTP channels to be played */
2197 g_list_foreach( rtp_streams_list, (GFunc)decode_rtp_stream, NULL);
2199 /* reset the number of frames to be displayed, this is used for the progress bar */
2201 /* Count the frames in all the RTP channels */
2202 if (rtp_channels_hash)
2203 g_hash_table_foreach( rtp_channels_hash, (GHFunc)count_channel_frames, NULL);
2205 /* reset the Progress Bar count again for the progress of creating the channels view */
2207 gtk_statusbar_pop((GtkStatusbar *) info_bar, statusbar_context);
2208 gtk_statusbar_push((GtkStatusbar *) info_bar, statusbar_context, " Creating channels view...");
2210 /* Display the RTP channels in the window */
2212 if (rtp_channels_hash)
2213 g_hash_table_foreach( rtp_channels_hash, (GHFunc)add_channel_to_window, &counter);
2215 /* Resize the main scroll window to display no more than preferred (or default) max channels, scroll bar will be used if needed */
2217 if (prefs.rtp_player_max_visible < 1 || prefs.rtp_player_max_visible > 10)
2218 prefs.rtp_player_max_visible = RTP_PLAYER_DEFAULT_VISIBLE;
2220 gtk_widget_set_size_request(main_scrolled_window, CHANNEL_WIDTH,
2221 MIN(counter, prefs.rtp_player_max_visible) * (CHANNEL_HEIGHT+60));
2223 gtk_widget_show_all(main_scrolled_window);
2225 gtk_widget_destroy(progress_bar);
2226 #if !GTK_CHECK_VERSION(3,0,0)
2227 gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(info_bar), TRUE);
2229 gtk_statusbar_pop((GtkStatusbar *) info_bar, statusbar_context);
2231 /* blank the status label */
2232 gtk_statusbar_pop((GtkStatusbar *) info_bar, statusbar_context);
2234 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2235 bt_state(TRUE, FALSE, FALSE, FALSE);
2237 /* get the static jitter buffer from the spinner gui */
2238 new_jitter_buff = (int) gtk_spin_button_get_value((GtkSpinButton * )jitter_spinner);
2242 /****************************************************************************/
2244 on_cb_view_as_time_of_day_clicked(GtkButton *button _U_, gpointer user_data _U_)
2246 /* Decode the streams again as if the decode button was pushed to update the time display */
2250 /****************************************************************************/
2252 on_cb_use_rtp_clicked(GtkToggleButton *button _U_, gpointer user_data _U_)
2254 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2255 bt_state(TRUE, FALSE, FALSE, FALSE);
2258 /****************************************************************************/
2260 on_bt_decode_clicked(GtkButton *button _U_, gpointer user_data _U_)
2265 /****************************************************************************/
2267 on_bt_play_clicked(GtkButton *button _U_, gpointer user_data _U_)
2272 /****************************************************************************/
2274 on_bt_pause_clicked(GtkButton *button _U_, gpointer user_data _U_)
2279 /****************************************************************************/
2281 on_bt_stop_clicked(GtkButton *button _U_, gpointer user_data _U_)
2286 /****************************************************************************/
2288 rtp_player_on_destroy(GObject *object _U_, gpointer user_data _U_)
2293 /* Stop the channels if necesary */
2294 if(rtp_channels && (!rtp_channels->stop)){
2298 /* Destroy the rtp channels */
2301 g_free(rtp_channels);
2302 rtp_channels = NULL;
2304 /* Terminate the use of PortAudio library */
2305 err = Pa_Terminate();
2306 initialized = FALSE;
2307 if( err != paNoError ) {
2308 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2309 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2310 "Can not terminate the PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
2311 gtk_dialog_run (GTK_DIALOG (dialog));
2312 gtk_widget_destroy (dialog);
2315 gtk_widget_destroy(rtp_player_dlg_w);
2316 main_scrolled_window = NULL;
2317 rtp_player_dlg_w = NULL;
2320 /****************************************************************************/
2322 jitter_spinner_value_changed (GtkSpinButton *spinner _U_, gpointer user_data _U_)
2324 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2325 bt_state(TRUE, TRUE, FALSE, FALSE);
2328 /****************************************************************************/
2330 rtp_player_dlg_create(void)
2333 GtkWidget *hbuttonbox;
2334 GtkWidget *timestamp_hb;
2335 GtkWidget *h_jitter_buttons_box;
2336 GtkWidget *bt_close;
2337 GtkAdjustment *jitter_spinner_adj;
2339 const gchar *title_name_ptr;
2342 title_name_ptr = cf_get_display_name(&cfile);
2343 win_name = g_strdup_printf("%s - VoIP - RTP Player", title_name_ptr);
2345 rtp_player_dlg_w = dlg_window_new(win_name); /* transient_for top_level */
2346 gtk_window_set_destroy_with_parent (GTK_WINDOW(rtp_player_dlg_w), TRUE);
2347 gtk_window_set_position(GTK_WINDOW(rtp_player_dlg_w), GTK_WIN_POS_NONE);
2349 gtk_window_set_default_size(GTK_WINDOW(rtp_player_dlg_w), 400, 50);
2351 main_vb = gtk_vbox_new (FALSE, 0);
2352 gtk_container_add(GTK_CONTAINER(rtp_player_dlg_w), main_vb);
2353 gtk_container_set_border_width (GTK_CONTAINER (main_vb), 2);
2355 main_scrolled_window=gtk_scrolled_window_new(NULL, NULL);
2356 gtk_container_set_border_width (GTK_CONTAINER (main_scrolled_window), 4);
2357 gtk_widget_set_size_request(main_scrolled_window, CHANNEL_WIDTH, 0);
2359 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (main_scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
2360 gtk_container_add(GTK_CONTAINER(main_vb), main_scrolled_window);
2362 channels_vb = gtk_vbox_new (FALSE, 0);
2363 gtk_container_set_border_width (GTK_CONTAINER (channels_vb), 2);
2364 gtk_scrolled_window_add_with_viewport((GtkScrolledWindow *) main_scrolled_window, channels_vb);
2366 timestamp_hb = gtk_hbox_new(FALSE, 0);
2367 gtk_box_pack_start(GTK_BOX(main_vb), timestamp_hb, FALSE, FALSE, 0);
2368 cb_view_as_time_of_day = gtk_check_button_new_with_label("View as time of day");
2369 gtk_box_pack_start(GTK_BOX(timestamp_hb), cb_view_as_time_of_day, TRUE, FALSE, 0); /* Centered */
2370 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_view_as_time_of_day), FALSE);
2371 gtk_widget_set_tooltip_text(cb_view_as_time_of_day, "View the timestamps as time of day instead of seconds since beginning of capture");
2372 g_signal_connect(cb_view_as_time_of_day, "toggled", G_CALLBACK(on_cb_view_as_time_of_day_clicked), NULL);
2374 h_jitter_buttons_box = gtk_hbox_new (FALSE, 0);
2375 gtk_container_set_border_width (GTK_CONTAINER (h_jitter_buttons_box), 10);
2376 gtk_box_pack_start (GTK_BOX(main_vb), h_jitter_buttons_box, FALSE, FALSE, 0);
2377 label = gtk_label_new("Jitter buffer [ms] ");
2378 gtk_box_pack_start(GTK_BOX(h_jitter_buttons_box), label, FALSE, FALSE, 0);
2380 jitter_spinner_adj = (GtkAdjustment *) gtk_adjustment_new (50, 0, 500, 5, 10, 0);
2381 jitter_spinner = gtk_spin_button_new (jitter_spinner_adj, 5, 0);
2382 gtk_box_pack_start(GTK_BOX(h_jitter_buttons_box), jitter_spinner, FALSE, FALSE, 0);
2383 gtk_widget_set_tooltip_text (jitter_spinner, "The simulated jitter buffer in [ms]");
2384 g_signal_connect(G_OBJECT (jitter_spinner_adj), "value_changed", G_CALLBACK(jitter_spinner_value_changed), NULL);
2386 cb_use_rtp_timestamp = gtk_check_button_new_with_label("Use RTP timestamp");
2387 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb_use_rtp_timestamp), FALSE);
2388 gtk_box_pack_start(GTK_BOX(h_jitter_buttons_box), cb_use_rtp_timestamp, FALSE, FALSE, 10);
2389 g_signal_connect(cb_use_rtp_timestamp, "toggled", G_CALLBACK(on_cb_use_rtp_clicked), NULL);
2390 gtk_widget_set_tooltip_text (cb_use_rtp_timestamp, "Use RTP Timestamp instead of the arriving packet time. This will not reproduce the RTP stream as the user heard it, but is useful when the RTP is being tunneled and the original packet timing is missing");
2393 hbuttonbox = gtk_hbutton_box_new ();
2394 gtk_box_pack_start (GTK_BOX (h_jitter_buttons_box), hbuttonbox, TRUE, TRUE, 0);
2395 gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox), GTK_BUTTONBOX_SPREAD);
2396 gtk_box_set_spacing (GTK_BOX (hbuttonbox), 10);
2398 bt_decode = gtk_button_new_from_stock(WIRESHARK_STOCK_DECODE);
2399 gtk_container_add(GTK_CONTAINER(hbuttonbox), bt_decode);
2400 g_signal_connect(bt_decode, "clicked", G_CALLBACK(on_bt_decode_clicked), NULL);
2401 gtk_widget_set_tooltip_text (bt_decode, "Decode the RTP stream(s)");
2403 bt_play = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PLAY);
2404 gtk_container_add(GTK_CONTAINER(hbuttonbox), bt_play);
2405 g_signal_connect(bt_play, "clicked", G_CALLBACK(on_bt_play_clicked), NULL);
2406 gtk_widget_set_tooltip_text (bt_play, "Play the RTP channel(s)");
2408 bt_pause = gtk_button_new_from_stock(GTK_STOCK_MEDIA_PAUSE);
2409 gtk_container_add(GTK_CONTAINER(hbuttonbox), bt_pause);
2410 g_signal_connect(bt_pause, "clicked", G_CALLBACK(on_bt_pause_clicked), NULL);
2411 gtk_widget_set_tooltip_text (bt_pause, "Pause the RTP channel(s)");
2413 bt_stop = gtk_button_new_from_stock(GTK_STOCK_MEDIA_STOP);
2414 gtk_container_add(GTK_CONTAINER(hbuttonbox), bt_stop);
2415 g_signal_connect(bt_stop, "clicked", G_CALLBACK(on_bt_stop_clicked), NULL);
2416 gtk_widget_set_tooltip_text (bt_stop, "Stop the RTP channel(s)");
2418 bt_close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
2419 gtk_container_add (GTK_CONTAINER (hbuttonbox), bt_close);
2420 #if GTK_CHECK_VERSION(2,18,0)
2421 gtk_widget_set_can_default(bt_close, TRUE);
2423 GTK_WIDGET_SET_FLAGS(bt_close, GTK_CAN_DEFAULT);
2425 gtk_widget_set_tooltip_text (bt_close, "Close this dialog");
2426 window_set_cancel_button(rtp_player_dlg_w, bt_close, window_cancel_button_cb);
2428 g_signal_connect(rtp_player_dlg_w, "delete_event", G_CALLBACK(window_delete_event_cb), NULL);
2429 g_signal_connect(rtp_player_dlg_w, "destroy", G_CALLBACK(rtp_player_on_destroy), NULL);
2432 hbuttonbox = gtk_hbutton_box_new ();
2434 /* Filter/status hbox */
2435 stat_hbox = gtk_hbox_new(FALSE, 1);
2436 gtk_container_set_border_width(GTK_CONTAINER(stat_hbox), 0);
2439 info_bar = gtk_statusbar_new();
2440 #if !GTK_CHECK_VERSION(3,0,0)
2441 gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(info_bar), TRUE);
2443 gtk_box_pack_start(GTK_BOX(stat_hbox), info_bar, TRUE, TRUE, 0);
2445 /* statusbar hbox */
2446 gtk_box_pack_start(GTK_BOX(main_vb), stat_hbox, FALSE, TRUE, 0);
2448 /* set the sensitive state of the buttons (decode, play, pause, stop) */
2449 bt_state(TRUE, FALSE, FALSE, FALSE);
2451 gtk_widget_show_all(rtp_player_dlg_w);
2453 /* Force gtk to redraw the window before starting decoding the packet */
2454 while (g_main_context_iteration(NULL, FALSE));
2459 /****************************************************************************/
2461 rtp_player_init(voip_calls_tapinfo_t *voip_calls_tap)
2466 if (initialized) return;
2469 voip_calls = voip_calls_tap;
2470 err = Pa_Initialize();
2471 if( err != paNoError ) {
2472 dialog = gtk_message_dialog_new ((GtkWindow *) rtp_player_dlg_w,
2473 GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE,
2474 "Can not Initialize the PortAudio Library.\n Error: %s", Pa_GetErrorText( err ));
2475 gtk_dialog_run (GTK_DIALOG (dialog));
2476 gtk_widget_destroy (dialog);
2477 initialized = FALSE;
2481 new_jitter_buff = -1;
2483 #ifdef HAVE_G729_G723
2484 /* Initialize the G729 and G723 decoders */
2487 #endif /* HAVE_G729_G723 */
2489 if (!rtp_channels) {
2490 rtp_channels = g_malloc(sizeof(rtp_play_channels_t));
2493 reset_rtp_channels();
2496 g_print("Pa_GetHostApiCount() = %d\n", Pa_GetHostApiCount());
2497 g_print("Pa_GetDefaultHostApi() = %d\n", Pa_GetDefaultHostApi());
2499 if ((Pa_GetHostApiCount() >= 0) && (Pa_GetDefaultHostApi() >= 0))
2502 PaHostApiIndex api_index;
2503 const PaHostApiInfo *api_info = Pa_GetHostApiInfo( (unsigned int)Pa_GetDefaultHostApi() );
2504 g_print("Default PaHostApiInfo.type = %d (%s)\n", api_info->type, api_info->name);
2506 for (i=0; i<(unsigned int)Pa_GetHostApiCount(); i++)
2508 api_info = Pa_GetHostApiInfo( i );
2509 g_print("PaHostApiInfo[%u].type = %d (%s)\n", i, api_info->type, api_info->name);
2512 api_index = Pa_HostApiTypeIdToHostApiIndex( paALSA );
2515 g_print("api_index for paALSA not found (%d)\n", api_index);
2519 api_info = Pa_GetHostApiInfo( (unsigned int)api_index );
2520 g_print("This should be ALSA: %s\n", api_info->name);
2525 /* create the dialog window */
2526 rtp_player_dlg_create();
2530 #endif /* HAVE_LIBPORTAUDIO */