/* * This file is designed to test mmapi's multithreaded programming */ #include "NvUtils.h" #include #include #include #include #include #include #include #include #include #include "NvCudaProc.h" #include "nvbuf_utils.h" #include "v4l2_nv_extensions.h" #include "v4l2_backend_test.h" #include #define TEST_ERROR(cond, str, label) if(cond) { \ cerr << str << endl; \ error = 1; \ goto label; } #define CHUNK_SIZE 4000000 #ifndef MIN #define MIN(a,b) (((a) < (b)) ? (a) : (b)) #endif #define NAL_UNIT_START_CODE 0x00000001 #define MIN_CHUNK_SIZE 50 #define USE_CPU_FOR_INTFLOAT_CONVERSION 0 static const int IMAGE_WIDTH = 1920; static const int IMAGE_HEIGHT = 1080; #define IS_NAL_UNIT_START(buffer_ptr) (!buffer_ptr[0] && !buffer_ptr[1] && \ !buffer_ptr[2] && (buffer_ptr[3] == 1)) #define IS_NAL_UNIT_START1(buffer_ptr) (!buffer_ptr[0] && !buffer_ptr[1] && \ (buffer_ptr[2] == 1)) using namespace std; global_cfg cfg; static int read_decoder_input_nalu(ifstream * stream, NvBuffer * buffer, char *parse_buffer, streamsize parse_buffer_size) { // Length is the size of the buffer in bytes char *buffer_ptr = (char *) buffer->planes[0].data; char *stream_ptr; bool nalu_found = false; streamsize bytes_read; streamsize stream_initial_pos = stream->tellg(); stream->read(parse_buffer, parse_buffer_size); bytes_read = stream->gcount(); if (bytes_read == 0) { return buffer->planes[0].bytesused = 0; } // Find the first NAL unit in the buffer stream_ptr = parse_buffer; while ((stream_ptr - parse_buffer) < (bytes_read - 3)) { nalu_found = IS_NAL_UNIT_START(stream_ptr) || IS_NAL_UNIT_START1(stream_ptr); if (nalu_found) { break; } stream_ptr++; } // Reached end of buffer but could not find NAL unit if (!nalu_found) { cerr << "Could not read nal unit from file. File seems to be corrupted" << endl; return -1; } memcpy(buffer_ptr, stream_ptr, 4); buffer_ptr += 4; buffer->planes[0].bytesused = 4; stream_ptr += 4; // Copy bytes till the next NAL unit is found while ((stream_ptr - parse_buffer) < (bytes_read - 3)) { if (IS_NAL_UNIT_START(stream_ptr) || IS_NAL_UNIT_START1(stream_ptr)) { streamsize seekto = stream_initial_pos + (stream_ptr - parse_buffer); if (stream->eof()) { stream->clear(); } stream->seekg(seekto, stream->beg); return 0; } *buffer_ptr = *stream_ptr; buffer_ptr++; stream_ptr++; buffer->planes[0].bytesused++; } // Reached end of buffer but could not find NAL unit cerr << "Could not read nal unit from file. File seems to be corrupted" << endl; return -1; } static nal_type_e parse_nalu_unit(NvBuffer * buffer) { unsigned char *pbuf = buffer->planes[0].data; return (nal_type_e)(*(pbuf + 4) & 0x1F); } static bool conv_output_dqbuf_thread_callback(struct v4l2_buffer *v4l2_buf, NvBuffer * buffer, NvBuffer * shared_buffer, void *arg) { context_t *ctx = (context_t *) arg; struct v4l2_buffer dec_capture_ret_buffer; struct v4l2_plane planes[MAX_PLANES]; memset(&dec_capture_ret_buffer, 0, sizeof(dec_capture_ret_buffer)); memset(planes, 0, sizeof(planes)); dec_capture_ret_buffer.index = shared_buffer->index; dec_capture_ret_buffer.m.planes = planes; pthread_mutex_lock(&ctx->queue_lock); ctx->conv_output_plane_buf_queue->push(buffer); // Return the buffer dequeued from converter output plane // back to decoder capture plane if (ctx->dec->capture_plane.qBuffer(dec_capture_ret_buffer, NULL) < 0) { pthread_cond_broadcast(&ctx->queue_cond); pthread_mutex_unlock(&ctx->queue_lock); return false; } pthread_cond_broadcast(&ctx->queue_cond); pthread_mutex_unlock(&ctx->queue_lock); return true; } static bool conv_capture_dqbuf_thread_callback(struct v4l2_buffer *v4l2_buf, NvBuffer * buffer, NvBuffer * shared_buffer, void *arg) { context_t *ctx = (context_t *) arg; Shared_Buffer batch_buffer; batch_buffer.bProcess = 0; ctx->renderer->render(buffer->planes[0].fd); if (ctx->conv->capture_plane.qBuffer(*v4l2_buf, NULL) < 0) { return false; } return true; } static void query_and_set_capture(context_t * ctx) { NvVideoDecoder *dec = ctx->dec; struct v4l2_format format; struct v4l2_crop crop; int32_t min_dec_capture_buffers; int ret = 0; int error = 0; uint32_t window_width; uint32_t window_height; char OSDcontent[512]; // Get capture plane format from the decoder. This may change after // an resolution change event ret = dec->capture_plane.getFormat(format); TEST_ERROR(ret < 0, "Error: Could not get format from decoder capture plane", error); // Get the display resolution from the decoder ret = dec->capture_plane.getCrop(crop); TEST_ERROR(ret < 0, "Error: Could not get crop from decoder capture plane", error); if (1)//(ctx->cpu_occupation_option == PARSER_DECODER_VIC_RENDER) { // Destroy the old instance of renderer as resolution might changed delete ctx->renderer; if (ctx->fullscreen) { // Required for fullscreen window_width = window_height = 0; } else if (ctx->window_width && ctx->window_height) { // As specified by user on commandline window_width = ctx->window_width; window_height = ctx->window_height; } else { // Resolution got from the decoder window_width = crop.c.width; window_height = crop.c.height; } // If height or width are set to zero, EglRenderer creates a fullscreen // window ctx->renderer = NvEglRenderer::createEglRenderer("renderer0", window_width, window_height, ctx->window_x, ctx->window_y); TEST_ERROR(!ctx->renderer, "Error in setting up renderer. " "Check if X is running or run with --disable-rendering", error); ctx->renderer->setFPS(ctx->fps); } // deinitPlane unmaps the buffers and calls REQBUFS with count 0 dec->capture_plane.deinitPlane(); // Not necessary to call VIDIOC_S_FMT on decoder capture plane. // But decoder setCapturePlaneFormat function updates the class variables ret = dec->setCapturePlaneFormat(format.fmt.pix_mp.pixelformat, format.fmt.pix_mp.width, format.fmt.pix_mp.height); TEST_ERROR(ret < 0, "Error in setting decoder capture plane format", error); // Get the minimum buffers which have to be requested on the capture plane ret = dec->getControl(V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, min_dec_capture_buffers); TEST_ERROR(ret < 0, "Error while getting value for V4L2_CID_MIN_BUFFERS_FOR_CAPTURE", error); // Request (min + 5) buffers, export and map buffers ret = dec->capture_plane.setupPlane(V4L2_MEMORY_MMAP, min_dec_capture_buffers + 5, false, false); TEST_ERROR(ret < 0, "Error in decoder capture plane setup", error); // For file write, first deinitialize output and capture planes // of video converter and then use the new resolution from // decoder event resolution change ctx->conv->waitForIdle(1000); ctx->conv->output_plane.stopDQThread(); ctx->conv->capture_plane.stopDQThread(); ctx->conv->output_plane.deinitPlane(); ctx->conv->capture_plane.deinitPlane(); while(!ctx->conv_output_plane_buf_queue->empty()) { ctx->conv_output_plane_buf_queue->pop(); } ret = ctx->conv->setOutputPlaneFormat(format.fmt.pix_mp.pixelformat, format.fmt.pix_mp.width, format.fmt.pix_mp.height, V4L2_NV_BUFFER_LAYOUT_BLOCKLINEAR); TEST_ERROR(ret < 0, "Error in converter output plane set format", error); ret = ctx->conv->setCapturePlaneFormat(format.fmt.pix_mp.pixelformat, crop.c.width, crop.c.height, V4L2_NV_BUFFER_LAYOUT_PITCH); TEST_ERROR(ret < 0, "Error in converter capture plane set format", error); ret = ctx->conv->setCropRect(0, 0, crop.c.width, crop.c.height); TEST_ERROR(ret < 0, "Error while setting crop rect", error); ret = ctx->conv->output_plane.setupPlane(V4L2_MEMORY_DMABUF, dec->capture_plane. getNumBuffers(), false, false); TEST_ERROR(ret < 0, "Error in converter output plane setup", error); ret = ctx->conv->capture_plane.setupPlane(V4L2_MEMORY_MMAP, dec->capture_plane. getNumBuffers(), true, false); TEST_ERROR(ret < 0, "Error in converter capture plane setup", error); ret = ctx->conv->output_plane.setStreamStatus(true); TEST_ERROR(ret < 0, "Error in converter output plane streamon", error); ret = ctx->conv->capture_plane.setStreamStatus(true); TEST_ERROR(ret < 0, "Error in converter output plane streamoff", error); // Add all empty conv output plane buffers to conv_output_plane_buf_queue for (uint32_t i = 0; i < ctx->conv->output_plane.getNumBuffers(); i++) { ctx->conv_output_plane_buf_queue->push(ctx->conv->output_plane. getNthBuffer(i)); } printf("DEBUG in %s, line %d\n", __func__, __LINE__); for (uint32_t i = 0; i < ctx->conv->capture_plane.getNumBuffers(); i++) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); v4l2_buf.index = i; v4l2_buf.m.planes = planes; ret = ctx->conv->capture_plane.qBuffer(v4l2_buf, NULL); TEST_ERROR(ret < 0, "Error Qing buffer at converter output plane", error); } printf("DEBUG in %s, line %d\n", __func__, __LINE__); ctx->conv->output_plane.startDQThread(ctx); ctx->conv->capture_plane.startDQThread(ctx); // Capture plane STREAMON ret = dec->capture_plane.setStreamStatus(true); TEST_ERROR(ret < 0, "Error in decoder capture plane streamon", error); // Enqueue all the empty capture plane buffers for (uint32_t i = 0; i < dec->capture_plane.getNumBuffers(); i++) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); v4l2_buf.index = i; v4l2_buf.m.planes = planes; ret = dec->capture_plane.qBuffer(v4l2_buf, NULL); TEST_ERROR(ret < 0, "Error Qing buffer at output plane", error); } cout << "Query and set capture successful" << endl; return; error: if (error) { ctx->got_error = true; cerr << "Error in " << __func__ << endl; } } static void * dec_capture_loop_fcn(void *arg) { context_t *ctx = (context_t *) arg; NvVideoDecoder *dec = ctx->dec; struct v4l2_event ev; int ret; cout << "Starting decoder capture loop thread" << endl; // Need to wait for the first Resolution change event, so that // the decoder knows the stream resolution and can allocate appropriate // buffers when we call REQBUFS do { ret = dec->dqEvent(ev, 3000); if (ret < 0) { if (errno == EAGAIN) { cerr << "Timed out waiting for first V4L2_EVENT_RESOLUTION_CHANGE" << endl; continue; } else { cerr << "Error in dequeueing decoder event" << endl; } ctx->got_error = true; break; } } while (ev.type != V4L2_EVENT_RESOLUTION_CHANGE); // query_and_set_capture acts on the resolution change event if (!ctx->got_error) query_and_set_capture(ctx); // Exit on error or EOS which is signalled in main() while (!(ctx->got_error || dec->isInError() || ctx->got_eos)) { NvBuffer *dec_buffer; // Check for Resolution change again ret = dec->dqEvent(ev, false); if (ret == 0) { switch (ev.type) { case V4L2_EVENT_RESOLUTION_CHANGE: query_and_set_capture(ctx); continue; } } while (1) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); v4l2_buf.m.planes = planes; // Dequeue a filled buffer if (dec->capture_plane.dqBuffer(v4l2_buf, &dec_buffer, NULL, 0)) { if (errno == EAGAIN) { usleep(1000); } else { ctx->got_error = true; cerr << "Error while calling dequeue at capture plane" << endl; } break; } // If we need to write to file, give the buffer to video converter output plane // instead of returning the buffer back to decoder capture plane NvBuffer *conv_buffer; struct v4l2_buffer conv_output_buffer; struct v4l2_plane conv_planes[MAX_PLANES]; memset(&conv_output_buffer, 0, sizeof(conv_output_buffer)); memset(conv_planes, 0, sizeof(conv_planes)); conv_output_buffer.m.planes = conv_planes; // Get an empty conv output plane buffer from conv_output_plane_buf_queue pthread_mutex_lock(&ctx->queue_lock); while (ctx->conv_output_plane_buf_queue->empty()) { pthread_cond_wait(&ctx->queue_cond, &ctx->queue_lock); } conv_buffer = ctx->conv_output_plane_buf_queue->front(); ctx->conv_output_plane_buf_queue->pop(); pthread_mutex_unlock(&ctx->queue_lock); conv_output_buffer.index = conv_buffer->index; if (ctx->conv->output_plane. qBuffer(conv_output_buffer, dec_buffer) < 0) { ctx->got_error = true; cerr << "Error while queueing buffer at converter output plane" << endl; break; } } } cout << "Exiting decoder capture loop thread" << endl; // Signal EOS to the decoder capture loop ctx->got_eos = true; //Signal VIC to wait EOS sem_post(&(ctx->dec_run_sem)); return NULL; } static void * dec_feed_loop_fcn(void *arg) { context_t *ctx = (context_t *) arg; int i = 0; bool eos = false; int ret; char *nalu_parse_buffer = NULL; nal_type_e nal_type; if (ctx->input_nalu) { nalu_parse_buffer = new char[CHUNK_SIZE]; } // Read encoded data and enqueue all the output plane buffers. // Exit loop in case file read is complete. while (!eos && !ctx->got_error && !ctx->dec->isInError() && i < (int)ctx->dec->output_plane.getNumBuffers()) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; NvBuffer *buffer; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); buffer = ctx->dec->output_plane.getNthBuffer(i); if (ctx->input_nalu) { read_decoder_input_nalu(ctx->in_file, buffer, nalu_parse_buffer, CHUNK_SIZE); } v4l2_buf.index = i; v4l2_buf.m.planes = planes; v4l2_buf.m.planes[0].bytesused = buffer->planes[0].bytesused; // It is necessary to queue an empty buffer to signal EOS to the decoder // i.e. set v4l2_buf.m.planes[0].bytesused = 0 and queue the buffer ret = ctx->dec->output_plane.qBuffer(v4l2_buf, NULL); if (ret < 0) { cerr << "Error Qing buffer at output plane" << endl; ctx->got_error = true; break; } if (v4l2_buf.m.planes[0].bytesused == 0) { eos = true; cout << "Input file read complete" << endl; break; } i++; } // Since all the output plane buffers have been queued, we first need to // dequeue a buffer from output plane before we can read new data into it // and queue it again. while (!eos && !ctx->got_error && !ctx->dec->isInError()) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; NvBuffer *buffer; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); v4l2_buf.m.planes = planes; ret = ctx->dec->output_plane.dqBuffer(v4l2_buf, &buffer, NULL, -1); if (ret < 0) { cerr << "Error DQing buffer at output plane" << endl; ctx->got_error = true; break; } if (ctx->input_nalu) { read_decoder_input_nalu(ctx->in_file, buffer, nalu_parse_buffer, CHUNK_SIZE); } v4l2_buf.m.planes[0].bytesused = buffer->planes[0].bytesused; ret = ctx->dec->output_plane.qBuffer(v4l2_buf, NULL); if (ret < 0) { cerr << "Error Qing buffer at output plane" << endl; ctx->got_error = true; break; } if (v4l2_buf.m.planes[0].bytesused == 0) { eos = true; cout << "Input file read complete" << endl; break; } } // After sending EOS, all the buffers from output plane should be dequeued. // and after that capture plane loop should be signalled to stop. while (ctx->dec->output_plane.getNumQueuedBuffers() > 0 && !ctx->got_error && !ctx->dec->isInError()) { struct v4l2_buffer v4l2_buf; struct v4l2_plane planes[MAX_PLANES]; memset(&v4l2_buf, 0, sizeof(v4l2_buf)); memset(planes, 0, sizeof(planes)); v4l2_buf.m.planes = planes; ret = ctx->dec->output_plane.dqBuffer(v4l2_buf, NULL, NULL, -1); if (ret < 0) { cerr << "Error DQing buffer at output plane" << endl; ctx->got_error = true; break; } } if (ctx->input_nalu) { delete []nalu_parse_buffer; } ctx->got_eos = true; return NULL; } static void set_defaults(context_t * ctx) { memset(ctx, 0, sizeof(context_t)); ctx->fullscreen = false; ctx->window_height = 0; ctx->window_width = 0; ctx->window_x = 0; ctx->window_y = 0; ctx->input_nalu = 1; ctx->fps = 30; ctx->do_stat = 0; ctx->cpu_occupation_option = 0; ctx->dec_status = 0; ctx->conv_output_plane_buf_queue = new queue < NvBuffer * >; ctx->render_buf_queue = new queue ; ctx->stop_render = 0; ctx->nvosd_context = NULL; } static void set_ctx_args(context_t * ctx, context_t * ctx_org) { //Reinitialize the structure memset(ctx, 0, sizeof(context_t)); ctx->fullscreen = false; ctx->window_height = 0; ctx->window_width = 0; ctx->window_x = 0; ctx->window_y = 0; ctx->input_nalu = 1; ctx->fps = 30; ctx->do_stat = 0; ctx->cpu_occupation_option = 0; ctx->dec_status = 0; ctx->conv_output_plane_buf_queue = new queue < NvBuffer * >; ctx->render_buf_queue = new queue ; ctx->stop_render = 0; ctx->nvosd_context = NULL; //Sets the parameters passed by the command line ctx->channel = ctx_org->channel; ctx->in_file_path = ctx_org->in_file_path; ctx->decoder_pixfmt = ctx_org->decoder_pixfmt; } static void get_disp_resolution(display_resolution_t *res) { if (NvEglRenderer::getDisplayResolution( res->window_width, res->window_height) < 0) { cerr << "get resolution failed, program will exit" << endl; exit(0); } return; } static void * multi_dec_main_loop_fcn(void *arg) { context_t *ctx_org = (context_t *) arg; context_t *ctx = (context_t*)calloc(1, sizeof(context_t)); display_resolution_t disp_info; int ret = 0; int error = 0; int loop_count = 0; while(1) { set_ctx_args(ctx, ctx_org); sem_init(&(ctx->dec_run_sem), 0, 0); get_disp_resolution(&disp_info); printf("enter %s[%d] loop_count %d\n", __func__, ctx->channel, loop_count); char decname[512]; sprintf(decname, "dec%d", ctx->channel); ctx->dec = NvVideoDecoder::createVideoDecoder(decname); TEST_ERROR(!ctx->dec, "Could not create decoder", cleanup); // Subscribe to Resolution change event ret = ctx->dec->subscribeEvent(V4L2_EVENT_RESOLUTION_CHANGE, 0, 0); TEST_ERROR(ret < 0, "Could not subscribe to V4L2_EVENT_RESOLUTION_CHANGE", cleanup); // Set V4L2_CID_MPEG_VIDEO_DISABLE_COMPLETE_FRAME_INPUT control to false // so that application can send chunks/slice of encoded data instead of // forming complete frames. This needs to be done before setting format // on the output plane. ret = ctx->dec->disableCompleteFrameInputBuffer(); TEST_ERROR(ret < 0, "Error in disableCompleteFrameInputBuffer", cleanup); // Set format on the output plane ret = ctx->dec->setOutputPlaneFormat( ctx->decoder_pixfmt, CHUNK_SIZE); TEST_ERROR(ret < 0, "Could not set output plane format", cleanup); // V4L2_CID_MPEG_VIDEO_DISABLE_DPB should be set after output plane // set format if (ctx->disable_dpb) { ret = ctx->dec->disableDPB(); TEST_ERROR(ret < 0, "Error in disableDPB", cleanup); } // Query, Export and Map the output plane buffers so that we can read // encoded data into the buffers ret = ctx->dec->output_plane.setupPlane( V4L2_MEMORY_MMAP, 10, true, false); TEST_ERROR(ret < 0, "Error while setting up output plane", cleanup); ctx->in_file = new ifstream(ctx->in_file_path); TEST_ERROR(!ctx->in_file->is_open(), "Error opening input file", cleanup); // Create converter to convert from BL to PL for writing raw video // to file char convname[512]; sprintf(convname, "conv%d", ctx->channel); ctx->conv = NvVideoConverter::createVideoConverter(convname); TEST_ERROR(!ctx->conv, "Could not create video converter", cleanup); ctx->conv->output_plane. setDQThreadCallback(conv_output_dqbuf_thread_callback); ctx->conv->capture_plane. setDQThreadCallback(conv_capture_dqbuf_thread_callback); ret = ctx->dec->output_plane.setStreamStatus(true); TEST_ERROR(ret < 0, "Error in output plane stream on", cleanup); if (cfg.channel_num == 1 && ctx->channel == 0) { ctx->window_width = disp_info.window_width; ctx->window_height = disp_info.window_height; ctx->window_x = 0; ctx->window_y = 0; } else { if (ctx->channel == 0) { ctx->window_width = disp_info.window_width / 2; ctx->window_height = disp_info.window_height / 2; ctx->window_x = 0; ctx->window_y = 0; } else if (ctx->channel == 1) { ctx->window_width = disp_info.window_width / 2; ctx->window_height = disp_info.window_height / 2; ctx->window_x = disp_info.window_width / 2; ctx->window_y = 0; } else if (ctx->channel == 2) { ctx->window_width = disp_info.window_width / 2; ctx->window_height = disp_info.window_height / 2; ctx->window_x = 0; ctx->window_y = disp_info.window_height / 2; } else { ctx->window_width = disp_info.window_width / 2; ctx->window_height = disp_info.window_height / 2; ctx->window_x = disp_info.window_width / 2; ctx->window_y = disp_info.window_height / 2; } } pthread_create(&ctx->dec_capture_loop, NULL, dec_capture_loop_fcn, ctx); pthread_create(&ctx->dec_feed_handle, NULL, dec_feed_loop_fcn, ctx); cleanup: sem_wait(&(ctx->dec_run_sem)); //we need wait to make sure decode get EOS ctx->conv->waitForIdle(-1); ctx->conv->capture_plane.stopDQThread(); ctx->conv->output_plane.stopDQThread(); pthread_join(ctx->dec_feed_handle, NULL); pthread_join(ctx->dec_capture_loop, NULL); if (ctx->dec->isInError()) { cerr << "Decoder is in error" << endl; error = 1; } if (ctx->got_error) { error = 1; } sem_destroy(&(ctx->dec_run_sem)); // The decoder destructor does all the cleanup i.e set streamoff on output and capture planes, // unmap buffers, tell decoder to deallocate buffer (reqbufs ioctl with counnt = 0), // and finally call v4l2_close on the fd. delete ctx->dec; delete ctx->conv; // Similarly, EglRenderer destructor does all the cleanup delete ctx->renderer; delete ctx->in_file; delete ctx->conv_output_plane_buf_queue; delete ctx->render_buf_queue; if (error) { cout << "App run failed" << endl; } else { cout << "App run was successful" << endl; } } } int main(int argc, char *argv[]) { context_t ctx[CHANNEL_NUM]; int iterator; char **argp; pthread_t main_loop_handle[CHANNEL_NUM]; memset(&cfg, 0, sizeof(global_cfg)); log_level = LOG_LEVEL_DEBUG; argp = argv; parse_global(&cfg, argc, &argp); if (parse_csv_args(&ctx[0], argc - cfg.channel_num - 1, argp)) { fprintf(stderr, "Error parsing commandline arguments\n"); return -1; } for (iterator = 0; iterator < cfg.channel_num; iterator++) { set_defaults(&ctx[iterator]); ctx[iterator].channel = iterator; if (parse_csv_args(&ctx[iterator], argc - cfg.channel_num - 1, argp)) { fprintf(stderr, "Error parsing commandline arguments\n"); return -1; } ctx[iterator].in_file_path = cfg.in_file_path[iterator]; pthread_create(&main_loop_handle[iterator], NULL, multi_dec_main_loop_fcn, &ctx[iterator]); } pause(); return 0; }