diff --git a/ChangeLog b/ChangeLog index f86b0af0..812e4e6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,6 +22,7 @@ Tim Blechmann * Remove checking thread in CoreAudio driver, better device state change recovery strategy: the driver is stopped and restarted. * jack_thread_wait implementation. * Add jack_thread_wait client example. + * Add jack_mp_thread_wait client example. 2008-01-30 Stephane Letz diff --git a/example-clients/mp_tw.cpp b/example-clients/mp_tw.cpp new file mode 100644 index 00000000..b3bb55ed --- /dev/null +++ b/example-clients/mp_tw.cpp @@ -0,0 +1,207 @@ + +/** @file mp_thread_client.c + * + * @brief This simple client demonstrates the use of "jack_thread_wait" function in a multi-threaded context. + + A set of n threads (the jack process thread + (n - 1) helper threads) are used to work on a global queue of tasks. + The last finishing thread gives control back to libjack using the "jack_thread_wait" function. Other threads suspend + on a condition variable and are resumed next cycle by the libjack suspended thread. +*/ + +#include +#include +#include +#include +#include +#include + +#define __SMP__ 1 + +#include +#include + +//#include "jack.h" +//#include "thread.h" +#include + +#include "JackAtomic.h" + +jack_port_t *input_port; +jack_port_t *output_port; +jack_client_t *client; +int buffer_size; + +#define WORK_AT_EACH_CYCLE 1000 +#define WORK_AT_EACH_LOOP 15 +static SInt32 cycle_work_count = 0; + +pthread_cond_t cond; +pthread_mutex_t mutex; + +jack_nframes_t last_time = 0; +static int print_count = 50; +int result = 0; + +typedef struct thread_context +{ + pthread_t thread; + int num; +}; + +// Simulate workload +static int fib(int n) +{ + if (n < 2) + return n; + else + return fib(n - 2) + fib(n - 1); +} + +static void do_some_work(void *arg) +{ + result = fib(WORK_AT_EACH_LOOP); +} + +static void resume_all_threads(void *arg) +{ + thread_context* context = (thread_context*)arg; + + jack_nframes_t cur_time = jack_frame_time(client); + if (--print_count == 0) { + printf("resume_all_threads from thread = %ld jack_frame_time = %u jack_cpu_load = %f\n", context->num, (cur_time - last_time), jack_cpu_load(client)); + print_count = 50; + } + pthread_mutex_lock(&mutex); // Hum... + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mutex); // Hum... + cycle_work_count = WORK_AT_EACH_CYCLE; + last_time = cur_time; +} + +static void suspend_jack_thread(void *arg) +{ + jack_thread_wait(client, 0); +} + +static void suspend_worker_thread(void *arg) +{ + pthread_mutex_lock(&mutex); // Hum... + pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); // Hum... +} + +static void * worker_aux_thread(void *arg) +{ + while (1) { + + int val = DEC_ATOMIC(&cycle_work_count); + + if (val == 1) { // Last thread + suspend_jack_thread(arg); + resume_all_threads(arg); + } else if (val < 1) { + suspend_worker_thread(arg); + } else { + do_some_work(arg); + } + } + + return 0; +} + +static void * worker_thread(void *arg) +{ + suspend_worker_thread(arg); // Start in "suspended" state + worker_aux_thread(arg); + return 0; +} + +// Example of audio process +int process(jack_nframes_t nframes, void *arg) +{ + resume_all_threads(arg); + worker_aux_thread(arg); + return 0; +} + +/** + * JACK calls this shutdown_callback if the server ever shuts down or + * decides to disconnect the client. + */ +void jack_shutdown (void *arg) +{ + exit(1); +} + +int main (int argc, char *argv[]) +{ + thread_context* worker_threads; + int n, nthreads = 0; + + if (argc == 2) + nthreads = atoi(argv[1]); + + worker_threads = (thread_context *) malloc (sizeof (thread_context) * nthreads); + + /* open a client connection to the JACK server */ + if ((client = jack_client_open("mp_thread_test", JackNoStartServer, NULL)) == NULL) { + fprintf(stderr, "Cannot open client\n"); + exit(1); + } + + buffer_size = jack_get_buffer_size(client); + + /* tell the JACK server to call the 'callback' function + */ + worker_threads[0].num = 0; + jack_set_process_callback(client, process, &worker_threads[0]); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + jack_on_shutdown(client, jack_shutdown, 0); + + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); + + input_port = jack_port_register(client, "input", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + output_port = jack_port_register(client, "output", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if ((input_port == NULL) || (output_port == NULL)) { + fprintf(stderr, "no more JACK ports available\n"); + exit(1); + } + + fprintf(stderr, "Creating %d threads\n", nthreads); + + for (n = 1; n <= nthreads; ++n) { + worker_threads[n].num = n; + if (jack_client_create_thread(client, &worker_threads[n].thread, 90, 1, worker_thread, &worker_threads[n]) < 0) + exit(1); + jack_acquire_real_time_scheduling (worker_threads[n].thread, 90); + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + + if (jack_activate(client)) { + fprintf(stderr, "cannot activate client"); + exit(1); + } + + while (1) { + #ifdef WIN32 + Sleep(1000); + #else + sleep(1); + #endif + } + + jack_client_close(client); + + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&cond); + + exit(0); +} diff --git a/example-clients/tw.c b/example-clients/tw.c new file mode 100644 index 00000000..3ba62cc2 --- /dev/null +++ b/example-clients/tw.c @@ -0,0 +1,204 @@ +/** @file simple_client.c + * + * @brief This simple client demonstrates the basic features of JACK + * as they would be used by many applications. + */ + +#include +#include +#include +#include +#include + +#include + +jack_port_t *input_port; +jack_port_t *output_port; +jack_client_t *client; + +/* a simple state machine for this client */ +volatile enum { + Init, + Run, + Exit +} client_state = Init; + +/** + * The process callback for this JACK application is called in a + * special realtime thread once for each audio cycle. + * + * This client follows a simple rule: when the JACK transport is + * running, copy the input port to the output. When it stops, exit. + */ +int +_process (jack_nframes_t nframes) +{ + jack_default_audio_sample_t *in, *out; + jack_transport_state_t ts = jack_transport_query(client, NULL); + + if (ts == JackTransportRolling) { + + if (client_state == Init) + client_state = Run; + + in = jack_port_get_buffer (input_port, nframes); + out = jack_port_get_buffer (output_port, nframes); + memcpy (out, in, + sizeof (jack_default_audio_sample_t) * nframes); + + } else if (ts == JackTransportStopped) { + + if (client_state == Run) + client_state = Exit; + } + + return 0; +} + +int +process (jack_nframes_t nframes, void* arg) +{ + jack_client_t* client = (jack_client_t*) arg; + + while ((nframes = jack_thread_wait (client, _process (nframes))) != 0); + + return 0; +} + +/** + * JACK calls this shutdown_callback if the server ever shuts down or + * decides to disconnect the client. + */ +void +jack_shutdown (void *arg) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + const char **ports; + const char *client_name; + const char *server_name = NULL; + jack_options_t options = JackNullOption; + jack_status_t status; + + if (argc >= 2) { /* client name specified? */ + client_name = argv[1]; + if (argc >= 3) { /* server name specified? */ + server_name = argv[2]; + options |= JackServerName; + } + } else { /* use basename of argv[0] */ + client_name = strrchr(argv[0], '/'); + if (client_name == 0) { + client_name = argv[0]; + } else { + client_name++; + } + } + + /* open a client connection to the JACK server */ + + client = jack_client_open (client_name, options, &status, server_name); + if (client == NULL) { + fprintf (stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + exit (1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(client); + fprintf (stderr, "unique name `%s' assigned\n", client_name); + } + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + + jack_set_process_callback (client, process, client); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + + jack_on_shutdown (client, jack_shutdown, 0); + + /* display the current sample rate. + */ + + printf ("engine sample rate: %" PRIu32 "\n", + jack_get_sample_rate (client)); + + /* create two ports */ + + input_port = jack_port_register (client, "input", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + output_port = jack_port_register (client, "output", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + + if ((input_port == NULL) || (output_port == NULL)) { + fprintf(stderr, "no more JACK ports available\n"); + exit (1); + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + exit (1); + } + + /* Connect the ports. You can't do this before the client is + * activated, because we can't make connections to clients + * that aren't running. Note the confusing (but necessary) + * orientation of the driver backend ports: playback ports are + * "input" to the backend, and capture ports are "output" from + * it. + */ + + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsOutput); + if (ports == NULL) { + fprintf(stderr, "no physical capture ports\n"); + exit (1); + } + + if (jack_connect (client, ports[0], jack_port_name (input_port))) { + fprintf (stderr, "cannot connect input ports\n"); + } + + free (ports); + + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsInput); + if (ports == NULL) { + fprintf(stderr, "no physical playback ports\n"); + exit (1); + } + + if (jack_connect (client, jack_port_name (output_port), ports[0])) { + fprintf (stderr, "cannot connect output ports\n"); + } + + free (ports); + + /* keep running until the transport stops */ + + while (client_state != Exit) { + sleep (1); + } + + jack_client_close (client); + exit (0); +} diff --git a/macosx/Jackdmp.xcodeproj/project.pbxproj b/macosx/Jackdmp.xcodeproj/project.pbxproj index b819055b..ca984044 100644 --- a/macosx/Jackdmp.xcodeproj/project.pbxproj +++ b/macosx/Jackdmp.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ /* Begin PBXBuildFile section */ 4B0A28ED0D520852002EFF74 /* tw.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A28EC0D520852002EFF74 /* tw.c */; }; 4B0A29260D52108E002EFF74 /* tw.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A28EC0D520852002EFF74 /* tw.c */; }; + 4B0A29830D523269002EFF74 /* mp_tw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A29820D523269002EFF74 /* mp_tw.cpp */; }; 4B35C41E0D4731D1000DE7AE /* Jackdmp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BF8D2250834F06A00C94B91 /* Jackdmp.cpp */; }; 4B35C4290D4731D1000DE7AE /* JackMachPort.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B799AD707899652003F3F15 /* JackMachPort.h */; }; 4B35C42A0D4731D1000DE7AE /* JackError.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BF8D1770834EE4800C94B91 /* JackError.h */; }; @@ -1007,6 +1008,8 @@ 4B0A28E60D52073D002EFF74 /* jack_thread_wait */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jack_thread_wait; sourceTree = BUILT_PRODUCTS_DIR; }; 4B0A28EC0D520852002EFF74 /* tw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = tw.c; path = "../example-clients/tw.c"; sourceTree = SOURCE_ROOT; }; 4B0A292D0D52108E002EFF74 /* jack_thread_wait */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jack_thread_wait; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B0A296D0D5231DC002EFF74 /* jack_mp_thread_wait */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = jack_mp_thread_wait; sourceTree = BUILT_PRODUCTS_DIR; }; + 4B0A29820D523269002EFF74 /* mp_tw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = mp_tw.cpp; path = "../example-clients/mp_tw.cpp"; sourceTree = SOURCE_ROOT; }; 4B123D3308B3954300540632 /* JackGlobalsClient.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = JackGlobalsClient.cpp; path = ../common/JackGlobalsClient.cpp; sourceTree = SOURCE_ROOT; }; 4B123D3608B3954A00540632 /* JackGlobalsServer.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = JackGlobalsServer.cpp; path = ../common/JackGlobalsServer.cpp; sourceTree = SOURCE_ROOT; }; 4B2C28F908DAD01E00249230 /* JackGlobals.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = JackGlobals.cpp; path = ../common/JackGlobals.cpp; sourceTree = SOURCE_ROOT; }; @@ -1236,6 +1239,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4B0A29670D5231DC002EFF74 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4B35C41F0D4731D1000DE7AE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1717,6 +1727,7 @@ 4B35C63E0D4731D3000DE7AE /* inprocess.so */, 4B0A28E60D52073D002EFF74 /* jack_thread_wait */, 4B0A292D0D52108E002EFF74 /* jack_thread_wait */, + 4B0A296D0D5231DC002EFF74 /* jack_mp_thread_wait */, ); name = Products; sourceTree = ""; @@ -1724,6 +1735,7 @@ 4B03383E0797E19900686131 /* Simple clients */ = { isa = PBXGroup; children = ( + 4B0A29820D523269002EFF74 /* mp_tw.cpp */, 4B0A28EC0D520852002EFF74 /* tw.c */, 4B5A1BDC0CD1CD420005BF74 /* midisine.c */, 4B5A1BBD0CD1CC110005BF74 /* midiseq.c */, @@ -2083,6 +2095,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4B0A29640D5231DC002EFF74 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4B35C41C0D4731D1000DE7AE /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -2736,6 +2755,25 @@ productReference = 4B0A292D0D52108E002EFF74 /* jack_thread_wait */; productType = "com.apple.product-type.tool"; }; + 4B0A29630D5231DC002EFF74 /* jack_mp_thread_wait */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4B0A29690D5231DC002EFF74 /* Build configuration list for PBXNativeTarget "jack_mp_thread_wait" */; + buildPhases = ( + 4B0A29640D5231DC002EFF74 /* Headers */, + 4B0A29650D5231DC002EFF74 /* Sources */, + 4B0A29670D5231DC002EFF74 /* Frameworks */, + 4B0A29680D5231DC002EFF74 /* Rez */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = jack_mp_thread_wait; + productInstallPath = /usr/local/bin; + productName = testSem; + productReference = 4B0A296D0D5231DC002EFF74 /* jack_mp_thread_wait */; + productType = "com.apple.product-type.tool"; + }; 4B35C41B0D4731D1000DE7AE /* jackdmp framework 64bits */ = { isa = PBXNativeTarget; buildConfigurationList = 4B35C4210D4731D1000DE7AE /* Build configuration list for PBXNativeTarget "jackdmp framework 64bits" */; @@ -3781,6 +3819,7 @@ 4BA692A60CBE4BC700EAD520 /* jack_load Universal */, 4BA692CA0CBE4C9000EAD520 /* jack_unload Universal */, 4B0A28DC0D52073D002EFF74 /* jack_thread_wait */, + 4B0A29630D5231DC002EFF74 /* jack_mp_thread_wait */, 4B699D4F097D421600A18468 /* synchroServer Universal */, 4B699D67097D421600A18468 /* synchroClient Universal */, 4B699D7F097D421700A18468 /* synchroServerClient Universal */, @@ -3880,6 +3919,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4B0A29680D5231DC002EFF74 /* Rez */ = { + isa = PBXRezBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4B35C4200D4731D1000DE7AE /* Rez */ = { isa = PBXRezBuildPhase; buildActionMask = 2147483647; @@ -4207,6 +4253,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4B0A29650D5231DC002EFF74 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4B0A29830D523269002EFF74 /* mp_tw.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4B35C41D0D4731D1000DE7AE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5338,6 +5392,96 @@ }; name = Default; }; + 4B0A296A0D5231DC002EFF74 /* Development */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + ); + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + HEADER_SEARCH_PATHS = ../common; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Jackmp, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = jack_mp_thread_wait; + REZ_EXECUTABLE = YES; + SDKROOT = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = YES; + }; + name = Development; + }; + 4B0A296B0D5231DC002EFF74 /* Deployment */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + ); + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_OPTIMIZATION_LEVEL = 3; + HEADER_SEARCH_PATHS = ../common; + MACOSX_DEPLOYMENT_TARGET = 10.4; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Jackmp, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = jack_mp_thread_wait; + REZ_EXECUTABLE = YES; + SDKROOT = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + ZERO_LINK = NO; + }; + name = Deployment; + }; + 4B0A296C0D5231DC002EFF74 /* Default */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + i386, + ppc, + ); + GCC_OPTIMIZATION_LEVEL = 3; + HEADER_SEARCH_PATHS = ../common; + OTHER_CFLAGS = ""; + OTHER_LDFLAGS = ( + "-framework", + Jackmp, + ); + OTHER_REZFLAGS = ""; + PRODUCT_NAME = jack_mp_thread_wait; + REZ_EXECUTABLE = YES; + SDKROOT = ""; + SECTORDER_FLAGS = ""; + WARNING_CFLAGS = ( + "-Wmost", + "-Wno-four-char-constants", + "-Wno-unknown-pragmas", + ); + }; + name = Default; + }; 4B35C4220D4731D1000DE7AE /* Development */ = { isa = XCBuildConfiguration; buildSettings = { @@ -11531,6 +11675,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Default; }; + 4B0A29690D5231DC002EFF74 /* Build configuration list for PBXNativeTarget "jack_mp_thread_wait" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4B0A296A0D5231DC002EFF74 /* Development */, + 4B0A296B0D5231DC002EFF74 /* Deployment */, + 4B0A296C0D5231DC002EFF74 /* Default */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Default; + }; 4B35C4210D4731D1000DE7AE /* Build configuration list for PBXNativeTarget "jackdmp framework 64bits" */ = { isa = XCConfigurationList; buildConfigurations = (