From: <sb...@us...> - 2007-09-26 19:13:31
|
Revision: 1156 http://iaxclient.svn.sourceforge.net/iaxclient/?rev=1156&view=rev Author: sbalea Date: 2007-09-26 12:13:29 -0700 (Wed, 26 Sep 2007) Log Message: ----------- Merge stresstest branch into trunk Modified Paths: -------------- trunk/configure.ac trunk/lib/Makefile.am trunk/lib/codec_ffmpeg.c trunk/lib/codec_theora.c trunk/lib/iaxclient.h trunk/lib/iaxclient_lib.c trunk/lib/libiax2/src/iax.c trunk/lib/video.c trunk/simpleclient/Makefile.am trunk/simpleclient/vtestcall/Makefile.am trunk/simpleclient/vtestcall/vtestcall.c Added Paths: ----------- trunk/lib/slice.c trunk/lib/slice.h trunk/simpleclient/stresstest/ trunk/simpleclient/stresstest/Makefile.am trunk/simpleclient/stresstest/README trunk/simpleclient/stresstest/file.c trunk/simpleclient/stresstest/file.h trunk/simpleclient/stresstest/stresstest.c trunk/simpleclient/stresstest/stresstest.vcproj Removed Paths: ------------- trunk/simpleclient/stresstest/Makefile.am trunk/simpleclient/stresstest/README trunk/simpleclient/stresstest/file.c trunk/simpleclient/stresstest/file.h trunk/simpleclient/stresstest/stresstest.c trunk/simpleclient/stresstest/stresstest.vcproj Modified: trunk/configure.ac =================================================================== --- trunk/configure.ac 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/configure.ac 2007-09-26 19:13:29 UTC (rev 1156) @@ -88,7 +88,7 @@ AC_ARG_ENABLE(clients, [AS_HELP_STRING([--enable-clients], - [Select clients (all iaxcomm iaxphone testcall tkphone vtestcall WinIAX wx) [default=auto]])],, + [Select clients (all iaxcomm iaxphone stresstest testcall tkphone vtestcall WinIAX wx) [default=auto]])],, enable_clients="auto") AC_ARG_WITH(ilbc, @@ -145,10 +145,10 @@ if test ! "x$enable_clients" = "xauto"; then for client in ${enable_clients}; do case "$client" in - iaxcomm | iaxphone | testcall | tkphone | vtestcall | WinIAX | wx) + iaxcomm | iaxphone | stresstest | testcall | tkphone | vtestcall | WinIAX | wx) clients="$clients $client" ;; all | yes) - clients="iaxcomm iaxphone testcall tkphone vtestcall WinIAX wx" + clients="iaxcomm iaxphone stresstest testcall tkphone vtestcall WinIAX wx" break ;; none | no) clients="" @@ -259,6 +259,7 @@ PKG_CHECK_MODULES(GTK, [gtk+-2.0 >= 2.0], has_gtk2=yes, has_gtk2=no) PKG_CHECK_MODULES(GDK2, [gdk-2.0 >= 2.0.0], has_gdk2=yes, has_gdk2=no) PKG_CHECK_MODULES(ALSA, [alsa >= 1.0], has_alsa=yes, has_alsa=no) +PKG_CHECK_MODULES(OGGZ, [oggz >= 0.9.5], has_oggz=yes, has_oggz=no) has_iax2=no if test ! x$enable_local_iax = xyes; then @@ -364,7 +365,7 @@ # Autodetect clients if test "x$enable_clients" = "xauto"; then - clients="$clients testcall" + clients="$clients testcall stresstest" if test ! x$has_wx = xno; then clients="$clients iaxphone" @@ -396,6 +397,9 @@ testcall) CLIENTS="$CLIENTS $client";; + stresstest) + CLIENTS="$CLIENTS $client";; + vtestcall) if test ! x$has_sdl = xyes || test ! x$with_video = xyes ; then AC_MSG_ERROR([vtestcall requires SDL and video]) @@ -445,6 +449,7 @@ simpleclient/Makefile iaxclient.pc simpleclient/testcall/Makefile + simpleclient/stresstest/Makefile simpleclient/vtestcall/Makefile simpleclient/iaxcomm/Makefile simpleclient/iaxphone/Makefile Modified: trunk/lib/Makefile.am =================================================================== --- trunk/lib/Makefile.am 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/Makefile.am 2007-09-26 19:13:29 UTC (rev 1156) @@ -146,6 +146,8 @@ ringbuffer.c \ ringbuffer.h \ portmixer/px_common/portmixer.h \ + slice.c \ + slice.h \ spandsp/plc.c \ spandsp/plc.h Modified: trunk/lib/codec_ffmpeg.c =================================================================== --- trunk/lib/codec_ffmpeg.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/codec_ffmpeg.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -13,6 +13,9 @@ * the GNU Lesser (Library) General Public License. * * A video codec using the ffmpeg library. + * + * TODO: this code still uses its own slicing mechanism + * It should be converted to use the API provided in slice.[ch] */ #include <stdlib.h> Modified: trunk/lib/codec_theora.c =================================================================== --- trunk/lib/codec_theora.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/codec_theora.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -32,24 +32,9 @@ * - No support for splitting the frame into multiple slices. Frames can * be relatively large. For a 320x240 video stream, you can see key * frames larger than 9KB, which is the maximum UDP packet size on Mac - * OS X. We split the encoded frame artificially into slices that will - * fit into a typical MTU. We also add six bytes at the beginning of - * each slice. - * - * - version: right now, first bit should be 0, the rest are undefined - * - * - source id: 2 bytes random number used to identify stream changes in - * conference applications this number is transmitted in big endian - * format over the wire - * - * - frame index number - used to detect a new frame when some of the - * slices of the current frame are missing (only the least significant - * 4 bits are used) - * - * - index of slice in the frame, starting at 0 - * - * - total number of slices in the frame - * + * OS X. To work around this limitation, we use the slice API to fragment + * encoded frames to a reasonable size that UDP can safely transport + * * Other miscellaneous comments: * * - For quality reasons, when we detect a video stream switch, we reject all @@ -68,34 +53,29 @@ #include <stdlib.h> #include "iaxclient_lib.h" #include "video.h" +#include "slice.h" #include "codec_theora.h" #include <theora/theora.h> #define MAX_SLICE_SIZE 8000 -#define MAX_ENCODED_FRAME_SIZE 48*1024 struct theora_decoder { - theora_state td; - theora_info ti; - theora_comment tc; - unsigned char frame_index; - unsigned char slice_count; - int frame_size; - unsigned short source_id; - int got_key_frame; - unsigned char buffer[MAX_ENCODED_FRAME_SIZE]; + theora_state td; + theora_info ti; + theora_comment tc; + struct deslicer_context *dsc; + int got_key_frame; }; struct theora_encoder { - theora_state td; - theora_info ti; - theora_comment tc; - int needs_padding; - unsigned char frame_index; - unsigned short source_id; - unsigned char *pad_buffer; + theora_state td; + theora_info ti; + theora_comment tc; + int needs_padding; + struct slicer_context *sc; + unsigned char *pad_buffer; }; static void destroy( struct iaxc_video_codec *c) @@ -111,6 +91,8 @@ e = (struct theora_encoder *)c->encstate; if ( e->pad_buffer ) free(e->pad_buffer); + if ( e->sc ) + free_slicer_context(e->sc); theora_comment_clear(&e->tc); theora_info_clear(&e->ti); theora_clear(&e->td); @@ -119,6 +101,8 @@ if ( c->decstate ) { d = (struct theora_decoder *)c->decstate; + if ( d->dsc ) + free_deslicer_context(d->dsc); theora_comment_clear(&d->tc); theora_info_clear(&d->ti); theora_clear(&d->td); @@ -127,25 +111,34 @@ free(c); } -static void reset_decoder_frame_state(struct theora_decoder * d) +static int decode(struct iaxc_video_codec *c, int inlen, char *in, int *outlen, char *out) { - memset(d->buffer, 0, MAX_ENCODED_FRAME_SIZE); - d->frame_size = 0; - d->slice_count = 0; -} + struct theora_decoder *d; + ogg_packet op; + yuv_buffer picture; + unsigned int line; + int my_out_len; + int w, h, ph; + int flen; + char *frame; -static int pass_frame_to_decoder(struct theora_decoder *d, int *outlen, char *out) -{ - ogg_packet op; - yuv_buffer picture; - unsigned int line; - int my_out_len; - int w, h, ph; + // Sanity checks + if ( !c || !c->decstate || !in || inlen <= 0 || !out || !outlen ) + return -1; + // Assemble slices + d = (struct theora_decoder *)c->decstate; + if ( !d->dsc ) + return -1; + + frame = deslice(in, inlen, &flen, d->dsc); + if ( frame == NULL ) + return 1; + /* decode into an OP structure */ memset(&op, 0, sizeof(op)); - op.bytes = d->frame_size; - op.packet = d->buffer; + op.bytes = flen; + op.packet = (unsigned char *)frame; /* reject all incoming frames until we get a key frame */ if ( !d->got_key_frame ) @@ -190,21 +183,19 @@ { // Y-even memcpy(out + picture.y_width * 2 * line, - picture.y + 2 * line * picture.y_stride, - picture.y_width); + picture.y + 2 * line * picture.y_stride, + picture.y_width); // Y-odd memcpy(out + picture.y_width * (2 * line + 1), - picture.y + (2 * line + 1) * picture.y_stride, - picture.y_width); + picture.y + (2 * line + 1) * picture.y_stride, + picture.y_width); // U + V - memcpy(out + (d->ti.frame_width * d->ti.frame_height) + - line * d->ti.frame_width / 2, - picture.u + line * picture.uv_stride, - picture.uv_width); - memcpy(out + (d->ti.frame_width * d->ti.frame_height * 5 / 4) + - line * d->ti.frame_width / 2, - picture.v + line * picture.uv_stride, - picture.uv_width); + memcpy(out + (d->ti.frame_width * d->ti.frame_height) + line * d->ti.frame_width / 2, + picture.u + line * picture.uv_stride, + picture.uv_width); + memcpy(out + (d->ti.frame_width * d->ti.frame_height * 5 / 4) + line * d->ti.frame_width / 2, + picture.v + line * picture.uv_stride, + picture.uv_width); } *outlen = my_out_len; @@ -212,96 +203,6 @@ return 0; } -static int decode(struct iaxc_video_codec *c, int inlen, char *in, int *outlen, char *out) -{ - struct theora_decoder *d; - unsigned char frame_index, slice_index, num_slices, version; - unsigned short source_id; - - // Sanity checks - if ( !c || !c->decstate || !in || inlen <= 0 || !out || !outlen ) - return -1; - - d = (struct theora_decoder *)c->decstate; - - version = *in++; - source_id = (unsigned short)(*in++) << 8; - source_id |= *in++; - frame_index = *in++ & 0x0f; - slice_index = *in++; - num_slices = *in++; - inlen -= 6; - - if ( version & 0x80 ) - { - fprintf(stderr, "Theora: unknown slice protocol\n"); - return -1; - } - - if ( source_id == d->source_id ) - { - /* We use only the least significant bits to calculate delta - * this helps with conferencing and video muting/unmuting - */ - unsigned char frame_delta = (frame_index - d->frame_index) & 0x0f; - - if ( frame_delta > 8 ) - { - /* Old slice coming in late, ignore. */ - return 1; - } else if ( frame_delta > 0 ) - { - /* Slice belongs to a new frame */ - d->frame_index = frame_index; - - if ( d->slice_count > 0 ) - { - /* Current frame is incomplete, drop it */ - c->video_stats.dropped_frames++; - reset_decoder_frame_state(d); - } - } - } else - { - /* Video stream was switched, the existing frame/slice - * indexes are meaningless. - */ - reset_decoder_frame_state(d); - d->source_id = source_id; - d->frame_index = frame_index; - d->got_key_frame = 0; - } - - // Process current slice - if ( c->fragsize * slice_index + inlen > MAX_ENCODED_FRAME_SIZE ) - { - // Frame would be too large, ignore slice - return -1; - } - - memcpy(d->buffer + c->fragsize * slice_index, in, inlen); - d->slice_count++; - - /* We only know the size of the frame when we get the final slice */ - if ( slice_index == num_slices - 1 ) - d->frame_size = c->fragsize * slice_index + inlen; - - if ( d->slice_count < num_slices ) - { - // we're still waiting for some slices - return 1; - } else - { - // Frame complete, send to decoder - int ret = pass_frame_to_decoder(d, outlen, out); - - // Clean up in preparation for next frame - reset_decoder_frame_state(d); - - return ret; - } -} - // Pads a w by h frame to bring it up to pw by ph size using value static void pad_channel(const char *src, int w, int h, unsigned char *dst, int pw, int ph, unsigned char value) @@ -329,8 +230,6 @@ static int encode(struct iaxc_video_codec *c, int inlen, char *in, struct slice_set_t *slice_set) { - int i, size, ssize; - const unsigned char *p; struct theora_encoder *e; ogg_packet op; yuv_buffer picture; @@ -402,41 +301,21 @@ // Check to see if we have a key frame slice_set->key_frame = theora_packet_iskeyframe(&op) == 1; + + // Slice the frame + slice((char *)op.packet, op.bytes, slice_set, e->sc); - // We need to split the frame into one or more slices - p = op.packet; - size = op.bytes; - - // Figure out how many slices we need - slice_set->num_slices = (size - 1) / c->fragsize + 1; - - // Copy up to fragsize bytes into each slice - for ( i = 0; i < slice_set->num_slices; i++ ) - { - slice_set->data[i][0] = 0; - slice_set->data[i][1] = (unsigned char)(e->source_id >> 8); - slice_set->data[i][2] = (unsigned char)(e->source_id & 0xff); - slice_set->data[i][3] = e->frame_index; - slice_set->data[i][4] = (unsigned char)i; - slice_set->data[i][5] = (unsigned char)slice_set->num_slices; - ssize = (i == slice_set->num_slices - 1) ? - size % c->fragsize : c->fragsize; - memcpy(&slice_set->data[i][6], p, ssize); - slice_set->size[i] = ssize + 6; - p += ssize; - } - e->frame_index++; - return 0; } struct iaxc_video_codec *codec_video_theora_new(int format, int w, int h, int framerate, int bitrate, int fragsize) { - struct iaxc_video_codec *c; - struct theora_encoder *e; - struct theora_decoder *d; - ogg_packet headerp, commentp, tablep; + struct iaxc_video_codec *c; + struct theora_encoder *e; + struct theora_decoder *d; + unsigned short source_id; + ogg_packet headerp, commentp, tablep; /* Basic sanity checks */ if ( w <= 0 || h <= 0 || framerate <= 0 || bitrate <= 0 || fragsize <= 0 ) @@ -470,6 +349,8 @@ if ( !c->encstate ) goto bail; + video_reset_codec_stats(c); + c->format = format; c->width = w; c->height = h; @@ -484,6 +365,15 @@ e = (struct theora_encoder *)c->encstate; d = (struct theora_decoder *)c->decstate; + // Initialize slicer + // Generate random source id + srand((unsigned int)time(0)); + source_id = rand() & 0xffff; + e->sc = create_slicer_context(source_id, fragsize); + if ( !e->sc ) + goto bail; + + /* set up some parameters in the contexts */ theora_info_init(&e->ti); @@ -576,11 +466,12 @@ if ( theora_decode_init(&d->td, &d->ti) ) goto bail; - // Generate random source id - srand((unsigned int)time(0)); - e->source_id = rand() & 0xffff; - d->got_key_frame = 0; + + // Initialize deslicer context + d->dsc = create_deslicer_context(c->fragsize); + if ( !d->dsc ) + goto bail; strcpy(c->name, "Theora"); return c; @@ -591,11 +482,19 @@ if ( c ) { if ( c->encstate ) + { + e = (struct theora_encoder *)c->encstate; + if ( e->sc ) + free_slicer_context(e->sc); free(c->encstate); - + } if ( c->decstate ) + { + d = (struct theora_decoder *)c->decstate; + if ( d->dsc ) + free_deslicer_context(d->dsc); free(c->decstate); - + } free(c); } Modified: trunk/lib/iaxclient.h =================================================================== --- trunk/lib/iaxclient.h 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/iaxclient.h 2007-09-26 19:13:29 UTC (rev 1156) @@ -22,7 +22,7 @@ #ifdef __cplusplus extern "C" { #endif - + /*! \file iaxclient.h \brief The IAXClient API @@ -1249,6 +1249,22 @@ */ EXPORT void iaxc_YUV420_to_RGB32(int width, int height, char *src, char *dest); + +/* + * Test mode functionality + * In test mode, iaxclient will do the following: + * - skip audio and video hardware initialization + * - wait for outgoing media to be provided by the main application + * - return incoming media to the calling application if required, via callbacks + * - not generate any meaningful statistics + * Test mode is designed to be used without a GUI, and with multiple instances of iaxclient + * running on the same machine. However, some applications might actually benefit from having + * this level of control. + * iaxc_set_test_mode() should be called before iaxc_initialize() + */ +EXPORT void iaxc_set_test_mode(int); +EXPORT int iaxc_push_audio(void *data, unsigned int size, unsigned int samples); +EXPORT int iaxc_push_video(void *data, unsigned int size, int fragment); #ifdef __cplusplus } #endif Modified: trunk/lib/iaxclient_lib.c =================================================================== --- trunk/lib/iaxclient_lib.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/iaxclient_lib.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -61,6 +61,9 @@ #undef JB_DEBUGGING +/* global test mode flag */ +int test_mode = 0; + /* configurable jitterbuffer options */ static long jb_target_extra = -1; @@ -615,33 +618,26 @@ strncpy(calls[i].callerid_number, DEFAULT_CALLERID_NUMBER, IAXC_EVENT_BUFSIZ); } + if ( !test_mode ) + { #ifndef AUDIO_ALSA - if ( pa_initialize(&audio_driver, 8000) ) - { - iaxci_usermsg(IAXC_ERROR, "failed pa_initialize"); - return -1; - } + if ( pa_initialize(&audio_driver, 8000) ) + { + iaxci_usermsg(IAXC_ERROR, "failed pa_initialize"); + return -1; + } #else - /* TODO: It is unknown whether this stuff for direct access to - * alsa should be left in iaxclient. We're leaving it in here for - * the time being, but unless it becomes clear that someone cares - * about having it, it will be removed. Also note that portaudio - * is capable of using alsa. This is another reason why this - * direct alsa access may be unneeded. - */ - if ( alsa_initialize(&audio_driver, 8000) ) - return -1; + /* TODO: It is unknown whether this stuff for direct access to + * alsa should be left in iaxclient. We're leaving it in here for + * the time being, but unless it becomes clear that someone cares + * about having it, it will be removed. Also note that portaudio + * is capable of using alsa. This is another reason why this + * direct alsa access may be unneeded. + */ + if ( alsa_initialize(&audio_driver, 8000) ) + return -1; #endif - - audio_format_capability = - IAXC_FORMAT_ULAW | - IAXC_FORMAT_ALAW | -#ifdef CODEC_GSM - IAXC_FORMAT_GSM | -#endif - IAXC_FORMAT_SPEEX; - audio_format_preferred = IAXC_FORMAT_SPEEX; - + } #ifdef USE_VIDEO if ( video_initialize() ) { @@ -650,6 +646,16 @@ } #endif + /* Default audio format capabilities */ + audio_format_capability = + IAXC_FORMAT_ULAW | + IAXC_FORMAT_ALAW | +#ifdef CODEC_GSM + IAXC_FORMAT_GSM | +#endif + IAXC_FORMAT_SPEEX; + audio_format_preferred = IAXC_FORMAT_SPEEX; + return 0; } @@ -660,9 +666,13 @@ get_iaxc_lock(); audio_driver.destroy(&audio_driver); + if ( !test_mode ) + { + audio_driver.destroy(&audio_driver); #ifdef USE_VIDEO - video_destroy(); + video_destroy(); #endif + } put_iaxc_lock(); #ifdef WIN32 @@ -749,7 +759,8 @@ get_iaxc_lock(); service_network(); - service_audio(); + if ( !test_mode ) + service_audio(); // Check registration refresh once a second if ( refresh_registration_count++ > 1000/LOOP_SLEEP ) @@ -783,7 +794,8 @@ else call = NULL; - video_send_video(call, selected_call); + if ( !test_mode ) + video_send_video(call, selected_call); video_send_stats(call); // Tight spinloops are bad, mmmkay? @@ -1098,8 +1110,10 @@ if ( iaxci_audio_output_mode ) continue; - audio_driver.output(&audio_driver, fr, - fr_samples - samples - mainbuf_delta); + if ( !test_mode ) + audio_driver.output(&audio_driver, fr, + fr_samples - samples - mainbuf_delta); + } while ( total_consumed < e->datalen ); } @@ -1784,6 +1798,9 @@ EXPORT int iaxc_audio_devices_get(struct iaxc_audio_device **devs, int *nDevs, int *input, int *output, int *ring) { + if ( test_mode ) + return 0; + *devs = audio_driver.devices; *nDevs = audio_driver.nDevices; audio_driver.selected_devices(&audio_driver, input, output, ring); @@ -1792,6 +1809,9 @@ EXPORT int iaxc_audio_devices_set(int input, int output, int ring) { + if ( test_mode ) + return 0; + int ret = 0; get_iaxc_lock(); ret = audio_driver.select_devices(&audio_driver, input, output, ring); @@ -1801,26 +1821,41 @@ EXPORT float iaxc_input_level_get() { + if ( test_mode ) + return 0; + return audio_driver.input_level_get(&audio_driver); } EXPORT float iaxc_output_level_get() { + if ( test_mode ) + return 0; + return audio_driver.output_level_get(&audio_driver); } EXPORT int iaxc_input_level_set(float level) { + if ( test_mode ) + return 0; + return audio_driver.input_level_set(&audio_driver, level); } EXPORT int iaxc_output_level_set(float level) { + if ( test_mode ) + return 0; + return audio_driver.output_level_set(&audio_driver, level); } EXPORT int iaxc_play_sound(struct iaxc_sound *s, int ring) { + if ( test_mode ) + return 0; + int ret = 0; get_iaxc_lock(); ret = audio_driver.play_sound(s,ring); @@ -1830,6 +1865,9 @@ EXPORT int iaxc_stop_sound(int id) { + if ( test_mode ) + return 0; + int ret = 0; get_iaxc_lock(); ret = audio_driver.stop_sound(id); @@ -1891,3 +1929,32 @@ audio_prefs = prefs; return 0; } + +EXPORT void iaxc_set_test_mode(int tm) +{ + test_mode = tm; +} + +EXPORT int iaxc_push_audio(void *data, unsigned int size, unsigned int samples) +{ + struct iaxc_call *call; + + if (selected_call < 0) + return -1; + + call = &calls[selected_call]; + + if ( audio_prefs & IAXC_AUDIO_PREF_SEND_DISABLE ) + return 0; + + //fprintf(stderr, "iaxc_push_audio: sending audio size %d\n", size); + + if ( iax_send_voice(call->session, call->format, data, size, samples) == -1 ) + { + fprintf(stderr, "iaxc_push_audio: failed to send audio frame of size %d on call %d\n", size, selected_call); + return -1; + } + + return 0; +} + Modified: trunk/lib/libiax2/src/iax.c =================================================================== --- trunk/lib/libiax2/src/iax.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/libiax2/src/iax.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -992,8 +992,9 @@ DEBU(G "Started on port %d\n", portno); } - srand((unsigned int)time(0)); - callnums = rand() % 32767 + 1; + //srand((unsigned int)time(0)); + //callnums = rand() % 32767 + 1; + callnums = 1; transfer_id = rand() % 32767 + 1; return portno; Copied: trunk/lib/slice.c (from rev 1155, branches/team/mihai/stresstest/stresstest/lib/slice.c) =================================================================== --- trunk/lib/slice.c (rev 0) +++ trunk/lib/slice.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,184 @@ +/* + * iaxclient: a portable telephony toolkit + * + * Copyright (C) 2007, Wimba, Inc. + * + * Mihai Balea <mihai at hates dot ms> + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * A codec independent frame slicer/assembler library + */ + +#include "slice.h" + +struct slicer_context * create_slicer_context(unsigned short source_id, unsigned int slice_size) +{ + struct slicer_context *sc; + + sc = calloc(1, sizeof(struct slicer_context)); + sc->source_id = source_id; + sc->slice_size = slice_size; + return sc; +} + +int free_slicer_context(struct slicer_context *sc) +{ + if ( sc == NULL ) + return -1; + + free(sc); + return 0; +} + +int slice(char *data, + unsigned int size, + struct slice_set_t *slice_set, + struct slicer_context *sc + ) +{ + int i, ssize; + + if ( data == NULL || slice_set == NULL || sc == NULL) + return -1; + + // Figure out how many slices we need + slice_set->num_slices = (size - 1) / sc->slice_size + 1; + + for ( i = 0; i < slice_set->num_slices; i++ ) + { + slice_set->data[i][0] = 0; + slice_set->data[i][1] = (unsigned char)(sc->source_id >> 8); + slice_set->data[i][2] = (unsigned char)(sc->source_id & 0xff); + slice_set->data[i][3] = sc->frame_index; + slice_set->data[i][4] = (unsigned char)i; + slice_set->data[i][5] = (unsigned char)slice_set->num_slices; + ssize = (i == slice_set->num_slices - 1) ? + size % sc->slice_size : sc->slice_size; + memcpy(&slice_set->data[i][6], data, ssize); + slice_set->size[i] = ssize + 6; + data += ssize; + } + sc->frame_index++; + + return 0; +} + +struct deslicer_context * create_deslicer_context(unsigned int slice_size) +{ + struct deslicer_context *dsc; + + dsc = calloc(1, sizeof(struct deslicer_context)); + dsc->slice_size = slice_size; + return dsc; +} + +int free_deslicer_context(struct deslicer_context *dsc) +{ + if ( dsc == NULL ) + return -1; + free(dsc); + return 0; +} + +static void reset_deslicer_context(struct deslicer_context *dsc) +{ + if ( dsc == NULL ) + return; + + memset(dsc->buffer, 0, sizeof(dsc->buffer)); + dsc->frame_size = 0; + dsc->slice_count = 0; + dsc->frame_complete = 0; +} + +char * deslice(char *in, int inlen, int *outlen, struct deslicer_context *dsc) +{ + unsigned char frame_index, slice_index, num_slices, version; + unsigned short source_id; + + // Sanity checks + if ( dsc == NULL || in == NULL || inlen <= 0 || outlen == NULL ) + return NULL; + + // If previous call returned a complete frame, clean up the context + if ( dsc->frame_complete ) + { + reset_deslicer_context(dsc); + } + + version = *in++; + source_id = (unsigned short)(*in++) << 8; + source_id |= *in++; + frame_index = *in++ & 0x0f; + slice_index = *in++; + num_slices = *in++; + inlen -= 6; + + if ( version & 0x80 ) + { + fprintf(stderr, "deslice: unknown slice protocol\n"); + return NULL; + } + + if ( source_id == dsc->source_id ) + { + /* We use only the least significant bits to calculate delta + * this helps with conferencing and video muting/unmuting + */ + unsigned char frame_delta = (frame_index - dsc->frame_index) & 0x0f; + + if ( frame_delta > 8 ) + { + /* Old slice coming in late, ignore. */ + return NULL; + } else if ( frame_delta > 0 ) + { + /* Slice belongs to a new frame */ + dsc->frame_index = frame_index; + + if ( dsc->slice_count > 0 ) + { + /* Current frame is incomplete, drop it */ + reset_deslicer_context(dsc); + } + } + } else + { + /* Video stream was switched, the existing frame/slice + * indexes are meaningless. + */ + reset_deslicer_context(dsc); + dsc->source_id = source_id; + dsc->frame_index = frame_index; + } + + // Process current slice + if ( dsc->slice_size * slice_index + inlen > MAX_ENCODED_FRAME_SIZE ) + { + // Frame would be too large, ignore slice + return NULL; + } + + memcpy(dsc->buffer + dsc->slice_size * slice_index, in, inlen); + dsc->slice_count++; + + /* We only know the size of the frame when we get the final slice */ + if ( slice_index == num_slices - 1 ) + { + dsc->frame_size = dsc->slice_size * slice_index + inlen; + } + + if ( dsc->slice_count < num_slices ) + { + // we're still waiting for some slices + return NULL; + } else + { + // Frame complete, set the flag and return the buffer + dsc->frame_complete = 1; + *outlen = dsc->frame_size; + return dsc->buffer; + } +} Copied: trunk/lib/slice.h (from rev 1155, branches/team/mihai/stresstest/stresstest/lib/slice.h) =================================================================== --- trunk/lib/slice.h (rev 0) +++ trunk/lib/slice.h 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,108 @@ +/* + * iaxclient: a portable telephony toolkit + * + * Copyright (C) 2007, Wimba, Inc. + * + * Mihai Balea <mihai at hates dot ms> + * + * This program is free software, distributed under the terms of + * the GNU Lesser (Library) General Public License + * + * A codec independent frame slicer/assembler library + */ + +/* + * This API can be used with codecs that do not provide internal support for + * splitting encoded frames in arbitrary-sized slices. This is useful for + * things like transmitting encoded frame over size constained packet + * protocols such as UDP. + * + * The slicer adds 6 bytes at the beginning of each slice. The + * format of this header is : + * - version: right now, first bit should be 0, the rest are undefined + * + * - source id: 2 bytes random number used to identify stream changes in + * conference applications this number is transmitted in big endian + * format over the wire + * + * - frame index number - used to detect a new frame when some of the + * slices of the current frame are missing (only the least significant + * 4 bits are used) + * + * - index of slice in the frame, starting at 0 + * + * - total number of slices in the frame + * + */ +#ifndef __SLICE_H__ +#define __SLICE_H__ + +#include "iaxclient_lib.h" + +#define MAX_ENCODED_FRAME_SIZE 48 * 1024 + +struct slicer_context +{ + unsigned char frame_index; + unsigned short source_id; + unsigned int slice_size; +}; + +struct deslicer_context +{ + unsigned char frame_index; + unsigned char slice_count; + int frame_size; + unsigned short source_id; + unsigned int slice_size; + int frame_complete; + char buffer[MAX_ENCODED_FRAME_SIZE]; +}; + +/* + * Allocates and initializes a slicer context with the given souirce_id + */ +struct slicer_context * create_slicer_context(unsigned short source_id, unsigned int slice_size); + +/* + * Deallocates a slicer_context + */ +int free_slicer_context(struct slicer_context *sc); + +/* + * Fragments a frame into one or several slices + * data - frame data + * size - size of frame data + * slice-set - pointer to a preallocated structure that will hold slices and slice information + * sc - holds stream information such as source id and frame index + * Returns 0 if completed successfully or a negative value if failure. + */ +int slice(char *data, + unsigned int size, + struct slice_set_t *slice_set, + struct slicer_context *sc + ); + +/* + * Allocates and initializes a deslicer context with the given souirce_id + */ +struct deslicer_context * create_deslicer_context(unsigned int slice_size); + +/* + * Deallocates a slicer_context + */ +int free_deslicer_context(struct deslicer_context *dsc); + +/* + * Assembles one frame out of multiple slices + * in - slice data + * inlen - length of slice + * outlen - length of assembled frame + * dsc - holds stream information + * Returns NULL if there is an error or the current frame is incomplete + * Returns a pointer to a buffer containing the completed frame and updates + * outlen with the frame size if successful + */ +char * deslice(char *in, int inlen, int *outlen, struct deslicer_context *dsc); + +#endif // __SLICE_H__ Modified: trunk/lib/video.c =================================================================== --- trunk/lib/video.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/lib/video.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -17,6 +17,7 @@ #include <assert.h> #include "video.h" +#include "slice.h" #include "iaxclient_lib.h" #include "videoLib/video_grab.h" #include "iax-client.h" @@ -30,6 +31,7 @@ #define VIDEO_BUFSIZ (1<<19) extern int selected_call; +extern int test_mode; extern struct iaxc_call * calls; static int iaxc_video_width = 320; @@ -41,6 +43,8 @@ static int iaxc_video_format_allowed = 0; static struct iaxc_video_driver video_driver; +static struct slice_set_t slice_set; + /* Set the default so that the local and remote raw video is * sent to the client application and encoded video is sent out. */ @@ -91,6 +95,11 @@ if ( prefs & ~prefs_mask ) return -1; + iaxc_video_prefs = prefs; + + if ( test_mode ) + return 0; + /* Not sending any video and not needing any form of * local video implies that we do not need to capture * video. @@ -118,8 +127,6 @@ } } - iaxc_video_prefs = prefs; - return 0; } @@ -456,7 +463,6 @@ /* try to get the next frame, encode and send */ int video_send_video(struct iaxc_call *call, int sel_call) { - static struct slice_set_t slice_set; int format; int i = 0; const int inlen = iaxc_video_width * iaxc_video_height * 6 / 4; @@ -690,18 +696,21 @@ int video_initialize(void) { - if ( pv_initialize(&video_driver, iaxc_video_width, iaxc_video_height, - iaxc_video_framerate) ) + if ( !test_mode ) { - fprintf(stderr, "ERROR: cannot initialize pv\n"); - return -1; + if ( pv_initialize(&video_driver, iaxc_video_width, iaxc_video_height, + iaxc_video_framerate) ) + { + fprintf(stderr, "ERROR: cannot initialize pv\n"); + return -1; + } + + /* We reset the existing video preferences to yield the side-effect + * of potentially starting or stopping the video capture. + */ + iaxc_set_video_prefs(iaxc_video_prefs); } - /* We reset the existing video preferences to yield the side-effect - * of potentially starting or stopping the video capture. - */ - iaxc_set_video_prefs(iaxc_video_prefs); - return 0; } @@ -863,3 +872,64 @@ return 0; } +void video_reset_codec_stats(struct iaxc_video_codec *vcodec) +{ + if ( vcodec == NULL ) return; + + memset(&vcodec->video_stats, 0, sizeof(struct iaxc_video_stats)); + gettimeofday(&vcodec->video_stats.start_time, NULL); +} + +static struct slicer_context *sc = NULL; + +EXPORT int iaxc_push_video(void *data, unsigned int size, int fragment) +{ + struct iaxc_call *call; + + if (selected_call < 0) + return -1; + + call = &calls[selected_call]; + + if ( iaxc_video_prefs & IAXC_VIDEO_PREF_SEND_DISABLE ) + return 0; + + //fprintf(stderr, "iaxc_push_video: sending video size %d\n", size); + + // Fragment if needed + if ( fragment ) + { + int i; + + if ( sc == NULL ) + sc = create_slicer_context(random(), iaxc_video_fragsize); + + slice(data, size, &slice_set, sc); + for ( i = 0 ; i < slice_set.num_slices ; i++ ) + { + if ( iax_send_video_trunk(call->session, + call->vformat, + slice_set.data[i], + slice_set.size[i], + 0, + i + ) == -1 + ) + { + fprintf(stderr, "Failed to send a slice, call %d, size %d\n", + selected_call, slice_set.size[i]); + return -1; + } + + } + } else + { + if ( iax_send_video_trunk(call->session, call->vformat, data, size, 0, 0) == -1 ) + { + fprintf(stderr, "iaxc_push_video: failed to send video frame of size %d on call %d\n", size, selected_call); + return -1; + } + } + + return 0; +} Modified: trunk/simpleclient/Makefile.am =================================================================== --- trunk/simpleclient/Makefile.am 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/Makefile.am 2007-09-26 19:13:29 UTC (rev 1156) @@ -4,6 +4,7 @@ WinIAX \ iaxcomm \ iaxphone \ + stresstest \ testcall \ tkphone \ vtestcall \ Copied: trunk/simpleclient/stresstest (from rev 1155, branches/team/mihai/stresstest/stresstest/simpleclient/stresstest) Deleted: trunk/simpleclient/stresstest/Makefile.am =================================================================== --- branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/Makefile.am 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/stresstest/Makefile.am 2007-09-26 19:13:29 UTC (rev 1156) @@ -1,7 +0,0 @@ -bin_PROGRAMS=stresstest -stresstest_SOURCES=stresstest.c file.c file.h - -AM_CPPFLAGS=-I$(top_srcdir)/lib $(SDL_CFLAGS) $(OGGZ_CFLAGS) $(THEORA_CFLAGS) -stresstest_LDADD=$(top_builddir)/lib/libiaxclient.la $(OGGZ_LIBS) $(THEORA_LIBS) - -EXTRA_DIST = stresstest.vcproj Copied: trunk/simpleclient/stresstest/Makefile.am (from rev 1155, branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/Makefile.am) =================================================================== --- trunk/simpleclient/stresstest/Makefile.am (rev 0) +++ trunk/simpleclient/stresstest/Makefile.am 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,7 @@ +bin_PROGRAMS=stresstest +stresstest_SOURCES=stresstest.c file.c file.h + +AM_CPPFLAGS=-I$(top_srcdir)/lib $(SDL_CFLAGS) $(OGGZ_CFLAGS) $(THEORA_CFLAGS) +stresstest_LDADD=$(top_builddir)/lib/libiaxclient.la $(OGGZ_LIBS) $(THEORA_LIBS) + +EXTRA_DIST = stresstest.vcproj Deleted: trunk/simpleclient/stresstest/README =================================================================== --- branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/README 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/stresstest/README 2007-09-26 19:13:29 UTC (rev 1156) @@ -1,24 +0,0 @@ -Use the following command line: - -vtestcall -F <codec> <framerate> <bps> <width> <height> <fragment size> -[destination] - -See iaxclient/lib/iaxclient.h for codec types. -Theora is 24 -H.264 is 21 - -Example: - -Theora stream, 15 fps, 200kbps, 320x240 -vtestcall -F 24 15 200000 320 240 1400 - -If destination is missing, vtestcall will wait for incoming calls. - -The fragment size parameter is now working. However, Asterisk seems to have -problems with frames bigger than 4K so don't go over that. - -Right now, you need all parameters, mainly because command line parsing in -vtestcall sucks and I am too lazy to fix it. If it bothers you, fix it -yourself and post a patch. - - Copied: trunk/simpleclient/stresstest/README (from rev 1155, branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/README) =================================================================== --- trunk/simpleclient/stresstest/README (rev 0) +++ trunk/simpleclient/stresstest/README 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,24 @@ +Use the following command line: + +vtestcall -F <codec> <framerate> <bps> <width> <height> <fragment size> +[destination] + +See iaxclient/lib/iaxclient.h for codec types. +Theora is 24 +H.264 is 21 + +Example: + +Theora stream, 15 fps, 200kbps, 320x240 +vtestcall -F 24 15 200000 320 240 1400 + +If destination is missing, vtestcall will wait for incoming calls. + +The fragment size parameter is now working. However, Asterisk seems to have +problems with frames bigger than 4K so don't go over that. + +Right now, you need all parameters, mainly because command line parsing in +vtestcall sucks and I am too lazy to fix it. If it bothers you, fix it +yourself and post a patch. + + Deleted: trunk/simpleclient/stresstest/file.c =================================================================== --- branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/file.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/stresstest/file.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -1,267 +0,0 @@ -#include <string.h> -#include <stdlib.h> -#include <stdio.h> -#include "file.h" - -static struct ogg_stream *audio_stream; -static struct ogg_stream *video_stream; - -struct op_node * create_node(ogg_packet *op, long serialno, long timestamp) -{ - struct op_node *node; - - node = malloc(sizeof(struct op_node)); - node->timestamp = timestamp; - node->serialno = serialno; - node->op = malloc(sizeof(*op)); - memcpy(node->op, op, sizeof(*op)); - node->op->packet = malloc(op->bytes); - memcpy(node->op->packet, op->packet, op->bytes); - - return node; -} - -void append_node(struct ogg_stream *os, struct op_node *node) -{ - if ( os->first == NULL ) - { - if ( os->last != NULL ) - { - fprintf(stderr, "Queue inconsistency, bailing...\n"); - return; - } - os->first = node; - os->last = node; - node->next = NULL; - } else - { - if ( os->last == NULL ) - { - fprintf(stderr, "Queue inconsistency, bailing...\n"); - return; - } - os->last->next = node; - os->last = node; - node->next = NULL; - } -} - -/* - * We're forced to use a dirty hack here, due to Theora's idiotic API - * Theora needs three separate pieces of data, called headers to initialize - * its internal decoder structure. After all three pieces have been received, - * we can call theora_decode_init. - * We use a counter and a flag to make sure we have decoded our three headers and then - * we call theora_decode_init so we can initialize a theora_state structure. - * We use the ts structure to convert a granule position into an actual timestamp. - * There are many ways in which this can fail, but we rely on having all three headers - * at the beginning of the ogg video bitstream. - * - * To whoever came up with this convoluted scheme: please consider a change of careers. - */ -int read_theora_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) -{ - struct op_node *node; - struct theora_headers *th; - long timestamp = 0; - - //fprintf(stderr, "Got theora packet, serialno=%d, size=%d, packetno=%lld, granulepos=%lld\n", serialno, op->bytes, op->packetno, op->granulepos); - - th = (struct theora_headers *)video_stream->data; - - if ( theora_packet_isheader(op) ) - { - theora_decode_header(&(th->ti), &(th->tc), op); - th->header_count++; - } - - if ( th->header_count >= 3 && !th->have_headers ) - { - theora_decode_init(&(th->ts), &(th->ti)); - th->have_headers = 1; - } - - if ( th->have_headers ) - { - double d; - - d = theora_granule_time(&(th->ts), op->granulepos); - timestamp = (long)(d * 1000); - } - - if ( timestamp < 0 ) - { - timestamp = video_stream->page_ts + video_stream->page_count * THEORA_FRAME_DURATION; - video_stream->page_count++; - } else - { - video_stream->page_ts = timestamp; - video_stream->page_count = 0; - } - - if ( !theora_packet_isheader(op) ) - { - node = create_node(op, serialno, timestamp); - append_node(video_stream, node); - } - - return 0; -} - -int read_speex_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) -{ - struct op_node *node; - long timestamp; - static int cnt = 0; - - timestamp = audio_stream->page_ts + audio_stream->page_count * SPEEX_FRAME_DURATION; - audio_stream->page_count++; - - cnt++; - //fprintf(stderr, "Got speex packet, serialno=%ld, size=%ld, packetno=%lld, granulepos=%lld, timestamp=%ld\n", serialno, op->bytes, op->packetno, op->granulepos, timestamp); - - // Ignore the first two packets, they are headers - if ( cnt > 2 ) - { - node = create_node(op, serialno, timestamp); - append_node(audio_stream, node); - } - - return 0; -} - -int read_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) -{ - struct theora_headers *th; - - const char theoraId[] = "\x80theora"; - const char speexId[] = "Speex "; - - if ( memcmp(op->packet, theoraId, strlen(theoraId)) == 0 ) - { - //fprintf(stderr, "Detected a Theora stream with serialno=%d\n", serialno); - oggz_set_read_callback(oggz, serialno, read_theora_cb, NULL); - video_stream->serialno = serialno; - - // Initialize theora specific data fields - th = (struct theora_headers *)calloc(1, sizeof(struct theora_headers)); - theora_info_init(&(th->ti)); - theora_comment_init(&(th->tc)); - video_stream->data = th; - - read_theora_cb(oggz, op, serialno, data); - } else if ( memcmp(op->packet, speexId, strlen(speexId)) == 0 ) - { - //fprintf(stderr, "Detected a Speex stream with serialno=%d\n", serialno); - oggz_set_read_callback(oggz, serialno, read_speex_cb, NULL); - audio_stream->serialno = serialno; - read_speex_cb(oggz, op, serialno, data); - } else - { - fprintf(stderr, "Got unknown ogg packet, serialno=%d, size=%d, packetno=%d, granulepos=%d\n", serialno, op->bytes, op->packetno, op->granulepos); - } - return 0; -} - -int read_page_cb(OGGZ *oggz, const ogg_page *og, long serialno, void *data) -{ - if ( serialno == audio_stream->serialno ) - { - audio_stream->page_ts = ogg_page_granulepos(og) * 1000 / SPEEX_SAMPLING_RATE; - audio_stream->page_count = 0; - } else if ( serialno == video_stream->serialno ) - { - //fprintf(stderr, "Got theora page serialno=%d, header_len=%d, body_len=%d, granulepos=%lld\n", serialno, og->header_len, og->body_len, ogg_page_granulepos(og)); - } - return 0; -} - -void dump_stream(struct ogg_stream *os) -{ - struct op_node *node; - - node = os->first; - while ( node != NULL ) - { - fprintf(stderr, "Size=%ld, Stream=%ld, packetno=%lld, timestamp=%ld\n", node->op->bytes, node->serialno, node->op->packetno, node->timestamp); - node = node->next; - } -} - -void load_ogg_file(const char *filename) -{ - OGGZ *oggz; - - oggz = oggz_open(filename, OGGZ_READ | OGGZ_AUTO); - if ( oggz == NULL ) - { - fprintf(stderr, "Error opening ogg file\n"); - } - fprintf(stderr, "Successfully opened ogg file %s\n", filename); - - // Initialize internal streams - audio_stream = calloc(1, sizeof(struct ogg_stream)); - video_stream = calloc(1, sizeof(struct ogg_stream)); - - oggz_set_read_callback(oggz, -1, read_cb, NULL); - oggz_set_read_page(oggz, -1, read_page_cb, NULL); - - oggz_run(oggz); - - //fprintf(stderr, "Audio stream, serialno=%d\n", audio_stream->serialno); - //dump_stream(audio_stream); - //fprintf(stderr, "Video stream, serialno=%d\n", video_stream->serialno); - //dump_stream(video_stream); - - oggz_close(oggz); -} - -ogg_packet * get_next_op(struct ogg_stream *os) -{ - ogg_packet *op; - struct timeval tv; - long time_now; - - if ( os == NULL ) - return NULL; - - gettimeofday(&tv, NULL); - time_now = tv.tv_sec * 1000 + tv.tv_usec / 1000; - - if ( os->current == NULL ) - { - // point to the beginning of the stream and reset the time base - os->base_ts = time_now; - os->current = os->first; - } - - op = NULL; - if ( os->current->timestamp < time_now - os->base_ts ) - { - op = os->current->op; - os->current = os->current->next; - } - - return op; -} - -ogg_packet * get_next_audio_op() -{ - return get_next_op(audio_stream); -} - -ogg_packet * get_next_video_op() -{ - return get_next_op(video_stream); -} - -int audio_is_eos() -{ - return audio_stream->current == NULL; -} - -int video_is_eos() -{ - return video_stream->current == NULL; -} - Copied: trunk/simpleclient/stresstest/file.c (from rev 1155, branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/file.c) =================================================================== --- trunk/simpleclient/stresstest/file.c (rev 0) +++ trunk/simpleclient/stresstest/file.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,267 @@ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include "file.h" + +static struct ogg_stream *audio_stream; +static struct ogg_stream *video_stream; + +struct op_node * create_node(ogg_packet *op, long serialno, long timestamp) +{ + struct op_node *node; + + node = malloc(sizeof(struct op_node)); + node->timestamp = timestamp; + node->serialno = serialno; + node->op = malloc(sizeof(*op)); + memcpy(node->op, op, sizeof(*op)); + node->op->packet = malloc(op->bytes); + memcpy(node->op->packet, op->packet, op->bytes); + + return node; +} + +void append_node(struct ogg_stream *os, struct op_node *node) +{ + if ( os->first == NULL ) + { + if ( os->last != NULL ) + { + fprintf(stderr, "Queue inconsistency, bailing...\n"); + return; + } + os->first = node; + os->last = node; + node->next = NULL; + } else + { + if ( os->last == NULL ) + { + fprintf(stderr, "Queue inconsistency, bailing...\n"); + return; + } + os->last->next = node; + os->last = node; + node->next = NULL; + } +} + +/* + * We're forced to use a dirty hack here, due to Theora's idiotic API + * Theora needs three separate pieces of data, called headers to initialize + * its internal decoder structure. After all three pieces have been received, + * we can call theora_decode_init. + * We use a counter and a flag to make sure we have decoded our three headers and then + * we call theora_decode_init so we can initialize a theora_state structure. + * We use the ts structure to convert a granule position into an actual timestamp. + * There are many ways in which this can fail, but we rely on having all three headers + * at the beginning of the ogg video bitstream. + * + * To whoever came up with this convoluted scheme: please consider a change of careers. + */ +int read_theora_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) +{ + struct op_node *node; + struct theora_headers *th; + long timestamp = 0; + + //fprintf(stderr, "Got theora packet, serialno=%d, size=%d, packetno=%lld, granulepos=%lld\n", serialno, op->bytes, op->packetno, op->granulepos); + + th = (struct theora_headers *)video_stream->data; + + if ( theora_packet_isheader(op) ) + { + theora_decode_header(&(th->ti), &(th->tc), op); + th->header_count++; + } + + if ( th->header_count >= 3 && !th->have_headers ) + { + theora_decode_init(&(th->ts), &(th->ti)); + th->have_headers = 1; + } + + if ( th->have_headers ) + { + double d; + + d = theora_granule_time(&(th->ts), op->granulepos); + timestamp = (long)(d * 1000); + } + + if ( timestamp < 0 ) + { + timestamp = video_stream->page_ts + video_stream->page_count * THEORA_FRAME_DURATION; + video_stream->page_count++; + } else + { + video_stream->page_ts = timestamp; + video_stream->page_count = 0; + } + + if ( !theora_packet_isheader(op) ) + { + node = create_node(op, serialno, timestamp); + append_node(video_stream, node); + } + + return 0; +} + +int read_speex_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) +{ + struct op_node *node; + long timestamp; + static int cnt = 0; + + timestamp = audio_stream->page_ts + audio_stream->page_count * SPEEX_FRAME_DURATION; + audio_stream->page_count++; + + cnt++; + //fprintf(stderr, "Got speex packet, serialno=%ld, size=%ld, packetno=%lld, granulepos=%lld, timestamp=%ld\n", serialno, op->bytes, op->packetno, op->granulepos, timestamp); + + // Ignore the first two packets, they are headers + if ( cnt > 2 ) + { + node = create_node(op, serialno, timestamp); + append_node(audio_stream, node); + } + + return 0; +} + +int read_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) +{ + struct theora_headers *th; + + const char theoraId[] = "\x80theora"; + const char speexId[] = "Speex "; + + if ( memcmp(op->packet, theoraId, strlen(theoraId)) == 0 ) + { + //fprintf(stderr, "Detected a Theora stream with serialno=%d\n", serialno); + oggz_set_read_callback(oggz, serialno, read_theora_cb, NULL); + video_stream->serialno = serialno; + + // Initialize theora specific data fields + th = (struct theora_headers *)calloc(1, sizeof(struct theora_headers)); + theora_info_init(&(th->ti)); + theora_comment_init(&(th->tc)); + video_stream->data = th; + + read_theora_cb(oggz, op, serialno, data); + } else if ( memcmp(op->packet, speexId, strlen(speexId)) == 0 ) + { + //fprintf(stderr, "Detected a Speex stream with serialno=%d\n", serialno); + oggz_set_read_callback(oggz, serialno, read_speex_cb, NULL); + audio_stream->serialno = serialno; + read_speex_cb(oggz, op, serialno, data); + } else + { + fprintf(stderr, "Got unknown ogg packet, serialno=%d, size=%d, packetno=%d, granulepos=%d\n", serialno, op->bytes, op->packetno, op->granulepos); + } + return 0; +} + +int read_page_cb(OGGZ *oggz, const ogg_page *og, long serialno, void *data) +{ + if ( serialno == audio_stream->serialno ) + { + audio_stream->page_ts = ogg_page_granulepos(og) * 1000 / SPEEX_SAMPLING_RATE; + audio_stream->page_count = 0; + } else if ( serialno == video_stream->serialno ) + { + //fprintf(stderr, "Got theora page serialno=%d, header_len=%d, body_len=%d, granulepos=%lld\n", serialno, og->header_len, og->body_len, ogg_page_granulepos(og)); + } + return 0; +} + +void dump_stream(struct ogg_stream *os) +{ + struct op_node *node; + + node = os->first; + while ( node != NULL ) + { + fprintf(stderr, "Size=%ld, Stream=%ld, packetno=%lld, timestamp=%ld\n", node->op->bytes, node->serialno, node->op->packetno, node->timestamp); + node = node->next; + } +} + +void load_ogg_file(const char *filename) +{ + OGGZ *oggz; + + oggz = oggz_open(filename, OGGZ_READ | OGGZ_AUTO); + if ( oggz == NULL ) + { + fprintf(stderr, "Error opening ogg file\n"); + } + fprintf(stderr, "Successfully opened ogg file %s\n", filename); + + // Initialize internal streams + audio_stream = calloc(1, sizeof(struct ogg_stream)); + video_stream = calloc(1, sizeof(struct ogg_stream)); + + oggz_set_read_callback(oggz, -1, read_cb, NULL); + oggz_set_read_page(oggz, -1, read_page_cb, NULL); + + oggz_run(oggz); + + //fprintf(stderr, "Audio stream, serialno=%d\n", audio_stream->serialno); + //dump_stream(audio_stream); + //fprintf(stderr, "Video stream, serialno=%d\n", video_stream->serialno); + //dump_stream(video_stream); + + oggz_close(oggz); +} + +ogg_packet * get_next_op(struct ogg_stream *os) +{ + ogg_packet *op; + struct timeval tv; + long time_now; + + if ( os == NULL ) + return NULL; + + gettimeofday(&tv, NULL); + time_now = tv.tv_sec * 1000 + tv.tv_usec / 1000; + + if ( os->current == NULL ) + { + // point to the beginning of the stream and reset the time base + os->base_ts = time_now; + os->current = os->first; + } + + op = NULL; + if ( os->current->timestamp < time_now - os->base_ts ) + { + op = os->current->op; + os->current = os->current->next; + } + + return op; +} + +ogg_packet * get_next_audio_op() +{ + return get_next_op(audio_stream); +} + +ogg_packet * get_next_video_op() +{ + return get_next_op(video_stream); +} + +int audio_is_eos() +{ + return audio_stream->current == NULL; +} + +int video_is_eos() +{ + return video_stream->current == NULL; +} + Deleted: trunk/simpleclient/stresstest/file.h =================================================================== --- branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/file.h 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/stresstest/file.h 2007-09-26 19:13:29 UTC (rev 1156) @@ -1,54 +0,0 @@ -#ifndef __FILE_H__ -#define __FILE_H__ - -#include <oggz/oggz.h> -#include <theora/theora.h> - -#define SPEEX_FRAME_DURATION 20 -#define SPEEX_SAMPLING_RATE 8000 - -#define THEORA_FRAME_DURATION 1000 / 15 - -// Struct used to build chains of packets for delivery -struct op_node -{ - ogg_packet *op; - long serialno; - long timestamp; - struct op_node *next; -}; - -struct ogg_stream -{ - struct op_node *first; - struct op_node *last; - struct op_node *current; - long serialno; - long page_ts; - long page_count; - long base_ts; - void *data; -}; - -struct theora_headers -{ - theora_info ti; - theora_comment tc; - theora_state ts; - int header_count; - int have_headers; -}; - -int read_theora_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); -int read_speex_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); -int read_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); -int read_page_cb(OGGZ *oggz, const ogg_page *og, long serialno, void *data); -void load_ogg_file(const char *filename); - -ogg_packet * get_next_audio_op(); -ogg_packet * get_next_video_op(); - -int audio_is_eos(); -int video_is_eos(); - -#endif // __FILE_H__ Copied: trunk/simpleclient/stresstest/file.h (from rev 1155, branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/file.h) =================================================================== --- trunk/simpleclient/stresstest/file.h (rev 0) +++ trunk/simpleclient/stresstest/file.h 2007-09-26 19:13:29 UTC (rev 1156) @@ -0,0 +1,54 @@ +#ifndef __FILE_H__ +#define __FILE_H__ + +#include <oggz/oggz.h> +#include <theora/theora.h> + +#define SPEEX_FRAME_DURATION 20 +#define SPEEX_SAMPLING_RATE 8000 + +#define THEORA_FRAME_DURATION 1000 / 15 + +// Struct used to build chains of packets for delivery +struct op_node +{ + ogg_packet *op; + long serialno; + long timestamp; + struct op_node *next; +}; + +struct ogg_stream +{ + struct op_node *first; + struct op_node *last; + struct op_node *current; + long serialno; + long page_ts; + long page_count; + long base_ts; + void *data; +}; + +struct theora_headers +{ + theora_info ti; + theora_comment tc; + theora_state ts; + int header_count; + int have_headers; +}; + +int read_theora_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); +int read_speex_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); +int read_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data); +int read_page_cb(OGGZ *oggz, const ogg_page *og, long serialno, void *data); +void load_ogg_file(const char *filename); + +ogg_packet * get_next_audio_op(); +ogg_packet * get_next_video_op(); + +int audio_is_eos(); +int video_is_eos(); + +#endif // __FILE_H__ Deleted: trunk/simpleclient/stresstest/stresstest.c =================================================================== --- branches/team/mihai/stresstest/stresstest/simpleclient/stresstest/stresstest.c 2007-09-26 15:11:22 UTC (rev 1155) +++ trunk/simpleclient/stresstest/stresstest.c 2007-09-26 19:13:29 UTC (rev 1156) @@ -1,353 +0,0 @@ -/* -* vtestcall: make a single video test call with IAXCLIENT -* -* IAX Support for talking to Asterisk and other Gnophone clients -* -* Copyright (C) 1999, Linux Support Services, Inc. -* -* Mark Spencer <mar...@li...> -* Stefano Falsetto <fal...@gn...> -* Mihai Balea <mihai AT hates DOT ms> -* -* This program is free software, distributed under the terms of -* the GNU Lesser (Library) General Public License -*/ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <fcntl.h> -#include <stdio.h> -#include <time.h> -#include <signal.h> - -#include "iaxclient.h" -#include "slice.h" -#include "file.h" - -#ifdef WIN32 -// Only under windows... -#undef main -#endif - -#define MAX_CALLS 1 - -//int format = IAXC_FORMAT_THEORA | IAXC_FORMAT_SPEEX; -int format = IAXC_FORMAT_H263 | IAXC_FORMAT_H263_PLUS | IAXC_FORMAT_H264 | IAXC_FORMAT_MPEG4 | IAXC_FORMAT_THEORA; -int formatp = IAXC_FORMAT_H264; //IAXC_FORMAT_THEORA; -int framerate = 15; -int bitrate = 200000; -int width = 320; -int height = 240; -int fragsize = 1400; - -int call_established = 0; - -// Forward declaration -void process_text_message(char *message); - -char caption[80] = ""; - -int send_video = 1; -int send_audio = 1; -int print_netstats = 0; - -// Audio-cosmetic... -struct iaxc_sound sound_ringOUT, sound_ringIN; - -/* routine called at exit to shutdown audio I/O and close nicely. -NOTE: If all this isnt done, the system doesnt not handle this -cleanly and has to be rebooted. What a pile of doo doo!! */ -void killem(void) -{ - fprintf(stderr,"Calling iaxc_shutdown..."); - iaxc_shutdown(); - fprintf(stderr,"Done\nProgram terminated correctly.\n"); - exit(0); -} - -void signal_handler(int signum) -{ - if ( signum == SIGTERM || signum == SIGINT ) - { - killem(); - exit(0); - } -} - -void fatal_error(char *err) { - killem(); - fprintf(stderr, "FATAL ERROR: %s\n", err); - exit(1); -} - -int levels_callback(float input, float output) { - //fprintf(stderr,"Input level: %f\nOutput level: %f\n",input,output); - return 1; -} - -int netstat_callback(struct iaxc_ev_netstats n) { - static int i; - - if ( !print_netstats ) - return 0; - - if(i++%25 == 0) - fprintf(stderr, "RTT\t" - "Rjit\tRlos%%\tRlosC\tRpkts\tRdel\tRdrop\tRooo\t" - "Ljit\tLlos%%\tLlosC\tLpkts\tLdel\tLdrop\tLooo\n"); - - fprintf(stderr, "%d\t" - "%d\t%d\t%d\t%d\t%d\t%d\t%d\t" - "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", - - n.rtt, - - n.remote.jitter, - n.remote.losspct, - n.remote.losscnt, - n.remote.packets, - n.remote.delay, - n.remote.dropped, - n.remote.ooo, - - n.local.jitter, - n.local.losspct, - n.local.losscnt, - n.local.packets, - n.local.delay, - n.local.dropped, - n.local.ooo - ); - - return 0; -} - -void hangup_and_exit(void) -{ - iaxc_dump_call(); - fprintf(stderr,"Dumped call\n"); - iaxc_millisleep(1000); - fprintf(stderr,"Sleeped for 1000 msec\n"); - iaxc_stop_processing_thread(); - fprintf(stderr,"Stopped processing thread\n"); - killem(); -} - -void process_text_message(char *message) -{ - unsigned int prefs; - - if ( strncmp(message, "CONTROL:", strlen("CONTROL:")) == 0 ) - { - message += strlen("CONTROL:"); - if ( strcmp(message, "STOPVIDEO") == 0 ) - { - // Stop sending video - prefs = iaxc_get_video_prefs(); - prefs = prefs | IAXC_VIDEO_PREF_SEND_DISABLE ; - iaxc_set_video_prefs(prefs); - } else if ( strcmp(message, "STARTVIDEO") == 0 ) ... [truncated message content] |