/* multimedia_sim.c v0.3 * * Dec 2002 - Miguel Freitas * * this is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * * this program is meant to simulate a dummy multimedia application and * measure how it would perform in a loaded system. it basicaly tries to * identify frame skipping problems that would have affected the movie * playback. * * although the model of threads is heavily based on xine's architecture, * its results should be also comparable with any other player program * like mplayer or avifile. the idea is to measure when the player isn't * scheduled in time for sending images at full frame rate and if X server * would also be scheduled in time for displaying. * * of course one might try some tricks to improve performance like decreasing * nice values for both XFree86 and player. however some distros don't * ship the X reniced and modifying desktop menu entries to add "nice" and * "sudo" commands is beyond most of users who just want to play their dvds... * * compile with: gcc -o multimedia_sim multimedia_sim.c -lpthread -lm * run as: ./multimedia_sim [nice_level] [test_duration] * * note1: default CPU_BURNING value should simulate more or less a mpeg2-class * decoding cpu usage. that will require, at least, a 300MHz processor. * * note2: a better simulation of xine's backend/frontend architecture would * also include another thread (frontend) to receive the xshm completion * events. i have intentionally not implemented it here. */ #include #include #include #include #include #include #define FRAME_PERIOD 1000000/30 /* NTSC period in us */ #define FRAME_SIZE 720*480*3/2 /* std resolution in yv12 format */ #define PREBUFFER_FRAMES 15 /* how many frames to "decode" ahead */ #define CPU_BURNING 8 /* reduce if your cpu isn't fast enough */ int nice_level = 0; int decoder_running; int video_out_running; int server_running; pthread_mutex_t counters_lock; pthread_cond_t wakeup_server; pthread_mutex_t queue_lock; pthread_cond_t enqueued; pthread_cond_t dequeued; int frames_sent = 0; int frames_received = 0; int frames_enqueued = 0; double start_time; pthread_mutex_t counters_lock; pthread_cond_t wakeup_server; /* statistics */ double total_latency = 0.0; double total_square = 0.0; double max_latency = 0.0; int frames_dropped = 0; int dropped_in_burst = 0; int bursts = 0; static void *alloc_frame( void ) { void *frame; frame = (void *)malloc(FRAME_SIZE); memset(frame,0,FRAME_SIZE); return frame; } static void dummy_enqueue_frame( void ) { pthread_mutex_lock( &queue_lock ); while( frames_enqueued == PREBUFFER_FRAMES ) pthread_cond_wait( &dequeued, &queue_lock ); frames_enqueued++; pthread_cond_signal( &enqueued ); pthread_mutex_unlock( &queue_lock ); } static void dummy_dequeue_frame( void ) { pthread_mutex_lock( &queue_lock ); while( !frames_enqueued ) pthread_cond_wait( &enqueued, &queue_lock ); frames_enqueued--; pthread_cond_signal( &dequeued ); pthread_mutex_unlock( &queue_lock ); } static double get_us_time( void ) { struct timeval tv; double us; gettimeofday(&tv, NULL); us = tv.tv_sec * 1e6; us += tv.tv_usec; return us; } /* from libmpeg2, used to burn cpu cycles */ #define W1 2841 /* 2048*sqrt (2)*cos (1*pi/16) */ #define W2 2676 /* 2048*sqrt (2)*cos (2*pi/16) */ #define W3 2408 /* 2048*sqrt (2)*cos (3*pi/16) */ #define W5 1609 /* 2048*sqrt (2)*cos (5*pi/16) */ #define W6 1108 /* 2048*sqrt (2)*cos (6*pi/16) */ #define W7 565 /* 2048*sqrt (2)*cos (7*pi/16) */ static void idct_row (int16_t * block) { int x0, x1, x2, x3, x4, x5, x6, x7, x8; x1 = block[4] << 11; x2 = block[6]; x3 = block[2]; x4 = block[1]; x5 = block[7]; x6 = block[5]; x7 = block[3]; x0 = (block[0] << 11) + 128; /* for proper rounding in the fourth stage */ /* first stage */ x8 = W7 * (x4 + x5); x4 = x8 + (W1 - W7) * x4; x5 = x8 - (W1 + W7) * x5; x8 = W3 * (x6 + x7); x6 = x8 - (W3 - W5) * x6; x7 = x8 - (W3 + W5) * x7; /* second stage */ x8 = x0 + x1; x0 -= x1; x1 = W6 * (x3 + x2); x2 = x1 - (W2 + W6) * x2; x3 = x1 + (W2 - W6) * x3; x1 = x4 + x6; x4 -= x6; x6 = x5 + x7; x5 -= x7; /* third stage */ x7 = x8 + x3; x8 -= x3; x3 = x0 + x2; x0 -= x2; x2 = (181 * (x4 + x5) + 128) >> 8; x4 = (181 * (x4 - x5) + 128) >> 8; /* fourth stage */ block[0] = (x7 + x1) >> 8; block[1] = (x3 + x2) >> 8; block[2] = (x0 + x4) >> 8; block[3] = (x8 + x6) >> 8; block[4] = (x8 - x6) >> 8; block[5] = (x0 - x4) >> 8; block[6] = (x3 - x2) >> 8; block[7] = (x7 - x1) >> 8; } static void *decoder_loop (void *this_gen) { int16_t *frame; int i,j; frame = alloc_frame(); /* dummy data */ for( i = 0; i < FRAME_SIZE/sizeof(int16_t); i++ ) frame[i] = i; while( decoder_running ) { /* eat some cpu cycles */ for( j = 0; j < CPU_BURNING; j++ ) for( i = 0; i < FRAME_SIZE/8/sizeof(int16_t); i+=8 ) idct_row( &frame[i] ); dummy_enqueue_frame(); } free(frame); pthread_exit(NULL); } static void *video_out_loop (void *this_gen) { double ttf; /* time to frame */ void *frame1, *frame2; nice(nice_level); frame1 = alloc_frame(); frame2 = alloc_frame(); start_time = get_us_time() + (FRAME_PERIOD * PREBUFFER_FRAMES); while( video_out_running ) { dummy_dequeue_frame(); /* eat some cpu cycles */ /*memcpy(frame1, frame2, FRAME_SIZE);*/ ttf = start_time + (frames_sent * FRAME_PERIOD); ttf -= get_us_time(); if( ttf > 0 ) usleep( ttf ); pthread_mutex_lock( &counters_lock ); frames_sent++; pthread_cond_signal( &wakeup_server ); pthread_mutex_unlock( &counters_lock ); } free(frame1); free(frame2); pthread_exit(NULL); } static void *server_loop (void *this_gen) { double estimated; double latency; void *frame1, *frame2; nice(nice_level); frame1 = alloc_frame(); frame2 = alloc_frame(); while( server_running ) { pthread_mutex_lock( &counters_lock ); while( frames_sent <= frames_received ) pthread_cond_wait( &wakeup_server, &counters_lock ); pthread_mutex_unlock( &counters_lock ); estimated = start_time + (frames_received * FRAME_PERIOD); latency = (get_us_time() - estimated)/1.0e6; if( latency > max_latency ) max_latency = latency; frames_received++; if( latency > FRAME_PERIOD/1.0e6 ) { frames_dropped++; dropped_in_burst++; } else if (dropped_in_burst) { dropped_in_burst = 0; bursts++; } total_latency += latency; total_square += latency * latency; /* eat some cpu cycles */ memcpy(frame1, frame2, FRAME_SIZE); } if (dropped_in_burst) bursts++; free(frame1); free(frame2); pthread_exit(NULL); } int main(int argc, char *argv[]) { pthread_t decoder_thread; pthread_t video_thread; pthread_t server_thread; void *p; double mean, var, stddev; double burst_size; double score; int duration = 10; if( argc > 1 ) { nice_level = atoi(argv[1]); printf("nice_level = %d\n", nice_level ); if( nice_level < 0 ) printf("(make sure you are root for negative nice)\n"); if( argc > 2 ) { duration = atoi(argv[2]); printf("duration = %d seconds\n", duration ); } } pthread_mutex_init (&counters_lock, NULL); pthread_cond_init (&wakeup_server, NULL); pthread_mutex_init (&queue_lock, NULL); pthread_cond_init (&enqueued, NULL); pthread_cond_init (&dequeued, NULL); server_running = 1; if ( pthread_create (&server_thread, NULL, server_loop, NULL) != 0) { printf("Error creating server thread.\n"); return 1; } video_out_running = 1; if ( pthread_create (&video_thread, NULL, video_out_loop, NULL) != 0) { printf("Error creating video thread.\n"); return 1; } decoder_running = 1; if ( pthread_create (&decoder_thread, NULL, decoder_loop, NULL) != 0) { printf("Error creating decoder thread.\n"); return 1; } sleep(duration); server_running = 0; pthread_join(server_thread,&p); video_out_running = 0; pthread_join(video_thread, &p); printf("[frames] received: %d dropped: %d\n", frames_received, frames_dropped ); if( bursts ) { burst_size = (double)frames_dropped / bursts; printf("[frames] mean dropped per burst: %lf\n", burst_size ); } else burst_size = 1.0; mean = total_latency / frames_received; var = total_square / frames_received - mean*mean; stddev = sqrt(var); printf("[latency] mean: %lf max: %lf stddev: %lf\n", mean, max_latency, stddev); /* score: lower is better. it tries to measure "how bad" the playback * was to the user. it counts fraction of dropped frames, if the dropped * frames were somewhat evenly distributed (instead of in bursts), and * also the mean and standard deviation. * note that the formula is actually arbitrary, i'm just trying to count * all these factors and weight them. */ score = 0.0; score += 0.90 * (double) frames_dropped / frames_received * sqrt(burst_size); score += 0.10 * (mean + stddev)/(FRAME_PERIOD/1.0e6); printf("score: %lf\n", score ); }