2727static const char * USAGE =
2828"pcaputil [options] INPUT OUTPUT\n"
2929"\n"
30- " Convert captured RTP packets in PCAP file to WAV file or play it\n"
30+ " Convert captured RTP packets in PCAP file to WAV/AVI file or play it\n"
3131" to audio device.\n"
3232"\n"
3333" INPUT is the PCAP file name/path.\n"
34- " OUTPUT is the WAV file name/path to store the output, or set to \"-\",\n"
34+ " OUTPUT is the WAV/AVI file name/path to store the output, or set to \"-\",\n"
3535" to play the output to audio device. The program will decode\n"
3636" the RTP contents using codec that is available in PJMEDIA,\n"
3737" and optionally decrypt the content using the SRTP crypto and\n"
@@ -46,6 +46,9 @@ static const char *USAGE =
4646"\n"
4747"Options for RTP packet processing:\n"
4848""
49+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
50+ " --video Video mode\n"
51+ #endif
4952" --codec=codec_id The codec ID formatted \"name/clock-rate/channel-count\"\n"
5053" must be specified for codec with dynamic PT,\n"
5154" e.g: \"Speex/8000\"\n"
@@ -81,6 +84,8 @@ static struct app
8184 pj_pcap_file * pcap ;
8285 pjmedia_port * wav ;
8386 pjmedia_codec * codec ;
87+ pjmedia_vid_codec * vcodec ;
88+ pjmedia_format vfmt ;
8489 pjmedia_aud_stream * aud_strm ;
8590 unsigned pt ;
8691 pjmedia_transport * srtp ;
@@ -90,6 +95,7 @@ static struct app
9095
9196struct args
9297{
98+ pj_bool_t video ;
9399 pj_str_t codec ;
94100 pj_str_t wav_filename ;
95101 pjmedia_aud_dev_index dev_id ;
@@ -125,10 +131,36 @@ static void cleanup()
125131 cmgr = pjmedia_endpt_get_codec_mgr (app .mept );
126132 pjmedia_codec_mgr_dealloc_codec (cmgr , app .codec );
127133 }
134+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
135+ if (app .vcodec ) {
136+ pjmedia_vid_codec_close (app .vcodec );
137+ pjmedia_vid_codec_mgr_dealloc_codec (NULL , app .vcodec );
138+ }
139+ #endif
128140 if (app .aud_strm ) {
129141 pjmedia_aud_stream_stop (app .aud_strm );
130142 pjmedia_aud_stream_destroy (app .aud_strm );
131143 }
144+
145+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
146+
147+ #if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC ) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
148+ pjmedia_codec_ffmpeg_vid_deinit ();
149+ #endif
150+ #if defined(PJMEDIA_HAS_VPX_CODEC ) && PJMEDIA_HAS_VPX_CODEC != 0
151+ pjmedia_codec_vpx_vid_deinit ();
152+ #endif
153+ #if defined(PJMEDIA_HAS_OPENH264_CODEC ) && PJMEDIA_HAS_OPENH264_CODEC != 0
154+ pjmedia_codec_openh264_vid_deinit ();
155+ #endif
156+ pjmedia_vid_dev_subsys_shutdown ();
157+ pjmedia_event_mgr_destroy (NULL );
158+ pjmedia_video_format_mgr_destroy (NULL );
159+ pjmedia_converter_mgr_destroy (NULL );
160+ pjmedia_vid_codec_mgr_destroy (pjmedia_vid_codec_mgr_instance ());
161+
162+ #endif
163+
132164 if (app .mept ) pjmedia_endpt_destroy (app .mept );
133165 if (app .pool ) pj_pool_release (app .pool );
134166 pj_caching_pool_destroy (& app .cp );
@@ -286,6 +318,7 @@ static pj_status_t play_cb(void *user_data, pjmedia_frame *f)
286318 return PJ_SUCCESS ;
287319}
288320
321+
289322static void pcap2wav (const struct args * args )
290323{
291324 const pj_str_t WAV = {".wav" , 4 };
@@ -296,6 +329,7 @@ static void pcap2wav(const struct args *args)
296329 pj_uint8_t * payload ;
297330 unsigned payload_len ;
298331 } pkt0 ;
332+
299333 pjmedia_codec_mgr * cmgr ;
300334 const pjmedia_codec_info * ci ;
301335 pjmedia_codec_param param ;
@@ -457,6 +491,161 @@ static void pcap2wav(const struct args *args)
457491}
458492
459493
494+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
495+ static pj_status_t event_cb (pjmedia_event * event , void * user_data )
496+ {
497+ PJ_UNUSED_ARG (user_data );
498+
499+ if (event -> epub == app .vcodec ) {
500+ /* This is codec event */
501+ switch (event -> type ) {
502+ case PJMEDIA_EVENT_FMT_CHANGED :
503+ {
504+ pjmedia_format * fmt = & event -> data .fmt_changed .new_fmt ;
505+
506+ /* The event may only provide width & height, re-initialize
507+ * format with fps, bps, etc.
508+ */
509+ pjmedia_format_init_video (& app .vfmt ,
510+ fmt -> id ,
511+ fmt -> det .vid .size .w ,
512+ fmt -> det .vid .size .h ,
513+ 25 , 1 );
514+ }
515+ break ;
516+ default :
517+ break ;
518+ }
519+ }
520+
521+ return PJ_SUCCESS ;
522+ }
523+ #endif
524+
525+
526+ static void pcap2avi (const struct args * args )
527+ {
528+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
529+ const pj_str_t AVI = {".avi" , 4 };
530+ enum { MAX_BUF_SIZE = 100 * 1024 * 1024 };
531+ struct pkt
532+ {
533+ pj_uint8_t buffer [PJMEDIA_MAX_MTU ];
534+ pjmedia_rtp_hdr * rtp ;
535+ pj_uint8_t * payload ;
536+ unsigned payload_len ;
537+ } pkt0 ;
538+
539+ const pjmedia_vid_codec_info * ci ;
540+ pjmedia_vid_codec_param param ;
541+ pjmedia_avi_streams * avi_streams = NULL ;
542+ pj_uint8_t * buf ;
543+ pj_status_t status ;
544+
545+ /* Create SRTP transport is needed */
546+ #if PJMEDIA_HAS_SRTP
547+ if (args -> srtp_crypto .slen ) {
548+ pjmedia_srtp_crypto crypto ;
549+ pjmedia_transport * tp ;
550+
551+ pj_bzero (& crypto , sizeof (crypto ));
552+ crypto .key = args -> srtp_key ;
553+ crypto .name = args -> srtp_crypto ;
554+ T ( pjmedia_transport_loop_create (app .mept , & tp ) );
555+ T ( pjmedia_transport_srtp_create (app .mept , tp , NULL , & app .srtp ) );
556+ T ( pjmedia_transport_srtp_start (app .srtp , & crypto , & crypto ) );
557+ }
558+ #endif
559+
560+ /* Read first packet */
561+ read_rtp (pkt0 .buffer , sizeof (pkt0 .buffer ), & pkt0 .rtp ,
562+ & pkt0 .payload , & pkt0 .payload_len , PJ_FALSE );
563+
564+ /* Get codec info and param for the specified payload type */
565+ app .pt = pkt0 .rtp -> pt ;
566+ if (app .pt < 96 ) {
567+ T ( pjmedia_vid_codec_mgr_get_codec_info (NULL , pkt0 .rtp -> pt , & ci ) );
568+ } else {
569+ unsigned cnt = 2 ;
570+ const pjmedia_vid_codec_info * info [2 ];
571+ T ( pjmedia_vid_codec_mgr_find_codecs_by_id (NULL , & args -> codec , & cnt ,
572+ info , NULL ) );
573+ if (cnt != 1 )
574+ err_exit ("Codec ID must be specified and unique!" , 0 );
575+
576+ ci = info [0 ];
577+ }
578+ T ( pjmedia_vid_codec_mgr_get_default_param (NULL , ci , & param ) );
579+ if (args -> codec_fmtp .slen > 0 ) {
580+ T ( pjmedia_stream_info_parse_fmtp_data (app .pool , & args -> codec_fmtp , & param .dec_fmtp ) );
581+ }
582+
583+ /* Alloc and init codec */
584+ T ( pjmedia_vid_codec_mgr_alloc_codec (NULL , ci , & app .vcodec ) );
585+ T ( pjmedia_vid_codec_init (app .vcodec , app .pool ) );
586+ T ( pjmedia_vid_codec_open (app .vcodec , & param ) );
587+
588+ /* Subscribe to codec events */
589+ pjmedia_event_subscribe (NULL , & event_cb , app .vcodec , app .vcodec );
590+
591+ /* Alloc buffer for decoded video frame */
592+ buf = (pj_uint8_t * )pj_pool_alloc (app .pool , MAX_BUF_SIZE );
593+
594+ /* Loop reading PCAP and writing AVI file */
595+ for (;;) {
596+ struct pkt pkt1 ;
597+ pjmedia_frame frame , out_frame ;
598+
599+ pj_bzero (& out_frame , sizeof (out_frame ));
600+ out_frame .buf = buf ;
601+ out_frame .size = MAX_BUF_SIZE ;
602+
603+ /* Decode and write to AVI file */
604+ pj_bzero (& frame , sizeof (frame ));
605+ frame .buf = pkt0 .payload ;
606+ frame .size = pkt0 .payload_len ;
607+ T ( pjmedia_vid_codec_decode (app .vcodec , 1 , & frame ,
608+ MAX_BUF_SIZE , & out_frame ) );
609+ if (out_frame .type == PJMEDIA_FRAME_TYPE_VIDEO && out_frame .size ) {
610+ if (!avi_streams && app .vfmt .id == PJMEDIA_FORMAT_I420 ) {
611+ /* Open AVI file */
612+ if (pj_stristr (& args -> wav_filename , & AVI )) {
613+ T ( pjmedia_avi_writer_create_streams (app .pool ,
614+ args -> wav_filename .ptr ,
615+ 1000 * 1024 * 1024 , /* max file size */
616+ 1 , & app .vfmt , 0 ,
617+ & avi_streams ) );
618+ pj_assert (avi_streams -> streams [0 ]);
619+ } else {
620+ err_exit ("invalid output file" , PJ_EINVAL );
621+ }
622+ }
623+
624+ if (avi_streams && avi_streams -> streams [0 ])
625+ T ( pjmedia_port_put_frame (avi_streams -> streams [0 ], & out_frame ) );
626+ }
627+
628+ /* Read next packet */
629+ if (!read_rtp (pkt1 .buffer , sizeof (pkt1 .buffer ), & pkt1 .rtp ,
630+ & pkt1 .payload , & pkt1 .payload_len , PJ_TRUE )) {
631+ break ;
632+ }
633+
634+ /* Next */
635+ pkt0 = pkt1 ;
636+ pkt0 .rtp = (pjmedia_rtp_hdr * )pkt0 .buffer ;
637+ pkt0 .payload = pkt0 .buffer + (pkt1 .payload - pkt1 .buffer );
638+ }
639+
640+ if (avi_streams && avi_streams -> streams [0 ])
641+ pjmedia_port_destroy (avi_streams -> streams [0 ]);
642+
643+ #else
644+ PJ_UNUSED_ARG (args );
645+ #endif
646+ }
647+
648+
460649int main (int argc , char * argv [])
461650{
462651 pj_str_t input ;
@@ -469,6 +658,7 @@ int main(int argc, char *argv[])
469658 OPT_DST_IP ,
470659 OPT_SRC_PORT ,
471660 OPT_DST_PORT ,
661+ OPT_VIDEO ,
472662 OPT_CODEC ,
473663 OPT_PLAY_DEV_ID ,
474664 OPT_CODEC_FMTP ,
@@ -486,6 +676,9 @@ int main(int argc, char *argv[])
486676 { "dst-ip" , 1 , 0 , OPT_DST_IP },
487677 { "src-port" , 1 , 0 , OPT_SRC_PORT },
488678 { "dst-port" , 1 , 0 , OPT_DST_PORT },
679+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
680+ { "video" , 0 , 0 , OPT_VIDEO },
681+ #endif
489682 { "codec" , 1 , 0 , OPT_CODEC },
490683 { "play-dev-id" , 1 , 0 , OPT_PLAY_DEV_ID },
491684 { "codec-fmtp" , 1 , 0 , OPT_CODEC_FMTP },
@@ -499,8 +692,7 @@ int main(int argc, char *argv[])
499692 int option_index ;
500693 char key_bin [32 ];
501694
502- args .srtp_crypto .slen = args .srtp_key .slen = 0 ;
503- args .codec .slen = 0 ;
695+ pj_bzero (& args , sizeof (args ));
504696 args .dev_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV ;
505697#if PJMEDIA_HAS_OPUS_CODEC
506698 args .opus_clock_rate = -1 ;
@@ -553,6 +745,11 @@ int main(int argc, char *argv[])
553745 case OPT_CODEC :
554746 args .codec = pj_str (pj_optarg );
555747 break ;
748+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
749+ case OPT_VIDEO :
750+ args .video = PJ_TRUE ;
751+ break ;
752+ #endif
556753 case OPT_PLAY_DEV_ID :
557754 args .dev_id = atoi (pj_optarg );
558755 break ;
@@ -595,10 +792,35 @@ int main(int argc, char *argv[])
595792 T ( pjlib_util_init () );
596793 T ( pjmedia_endpt_create (& app .cp .factory , NULL , 0 , & app .mept ) );
597794
795+ #if defined(PJMEDIA_HAS_VIDEO ) && (PJMEDIA_HAS_VIDEO != 0 )
796+
797+ /* Video subsystem init */
798+ T ( pjmedia_vid_dev_subsys_init (& app .cp .factory ) );
799+ T ( pjmedia_converter_mgr_create (app .pool , NULL ) );
800+ T ( pjmedia_event_mgr_create (app .pool , 0 , NULL ) );
801+ T ( pjmedia_vid_codec_mgr_create (app .pool , NULL ) );
802+ T (pjmedia_video_format_mgr_create (app .pool , 64 , 0 , NULL ) );
803+
804+ #if defined(PJMEDIA_HAS_OPENH264_CODEC ) && PJMEDIA_HAS_OPENH264_CODEC != 0
805+ T ( pjmedia_codec_openh264_vid_init (NULL , & app .cp .factory ) );
806+ #endif
807+ #if defined(PJMEDIA_HAS_VPX_CODEC ) && PJMEDIA_HAS_VPX_CODEC != 0
808+ T ( pjmedia_codec_vpx_vid_init (NULL , & app .cp .factory ) );
809+ #endif
810+ #if defined(PJMEDIA_HAS_FFMPEG_VID_CODEC ) && PJMEDIA_HAS_FFMPEG_VID_CODEC != 0
811+ T ( pjmedia_codec_ffmpeg_vid_init (NULL , & app .cp .factory ) );
812+ #endif
813+
814+ #endif
815+
598816 T ( pj_pcap_open (app .pool , input .ptr , & app .pcap ) );
599817 T ( pj_pcap_set_filter (app .pcap , & filter ) );
600818
601- pcap2wav (& args );
819+ if (args .video ) {
820+ pcap2avi (& args );
821+ } else {
822+ pcap2wav (& args );
823+ }
602824
603825 cleanup ();
604826 return 0 ;
0 commit comments