Many people want to implement live video transmission based on live555. in Linux, webcam has a v4l architecture. in Windows, there are two options: VFW and DirectShow. however, the DirectShow architecture is very independent. If you want to use it, you have to create a render filter that can be packaged and sent by RTP. It is difficult to use the live555 architecture. therefore, VFW is the best choice.
This article analyzes the implementation of FFMPEG vfwcap, and also helps understand FFMPEG architecture and inputdevice writing.
To implement an input device, first implement a structure:
Avinputformat ff_vfwcap_demuxer = {
. Name = "vfwcap ",
. Long_name = null_if_config_small ("VFW video capture "),
. Priv_data_size = sizeof (struct vfw_ctx ),
. Read_header = vfw_read_header,
. Read_packet = vfw_read_packet,
. Read_close = vfw_read_close,
. Flags = avfmt_nofile,
. Priv_class = & vfw_class,
};
We can see that the input device is actually an input format. The read header and the read packet and read close functions are implemented here, which are called when the input device is initialized, read, and closed respectively.
Let's take a look at the read header function first:
Let's take a look at the comments. It's very comprehensive.
Static int vfw_read_header (avformatcontext * s, avformatparameters * AP) {// vfw_ctx is the object and parameter required to use the vfw api and is specialized data, stored in priv_data, struct vfw_ctx * CTX = s-> priv_data; avcodeccontext * codec; avstream * st; int devnum; int bisize; bitmapinfo * bi = NULL; captureparms cparms; DWORD bicompression; word bibitcount; int ret; avrational framerate_q; If (! Strcmp (S-> filename, "list") {for (devnum = 0; devnum <= 9; devnum ++) {char driver_name [256]; char driver_ver [256]; // get VFW driver information ret = capgetdriverdescription (devnum, driver_name, sizeof (driver_name), driver_ver, sizeof (driver_ver); If (RET) {av_log (S, av_log_info, "Driver % d \ n", devnum); av_log (S, av_log_info, "% s \ n", driver_name); av_log (S, av_log_info, "% s \ n", driver_ver) ;}return averror (EIO) ;}// call W The in API function creates a window related to the VFW capture device. // hwnd_message is a custom window message used to respond to messages sent from the VFW device. CTX-> hwnd = capcreatecapturewindow (null, 0, 0, 0, 0, 0, hwnd_message, 0); If (! CTX-> hwnd) {av_log (S, av_log_error, "cocould not create capture window. \ n "); Return averror (EIO);} // s-> filename is received from the command line, representing the VFW device serial number. The default value is 0. /* If atoi fails, devnum = 0 and the default device is used */devnum = atoi (S-> filename ); // connect to the device ret = sendmessage (CTX-> hwnd, wm_cap_driver_connect, devnum, 0); If (! RET) {av_log (S, av_log_error, "cocould not connect to device. \ n "); destroywindow (CTX-> hwnd); Return averror (enodev);} // set the parameter: Do not overlay or preview the parameter? I guess I didn't check msdn. Sendmessage (CTX-> hwnd, wm_cap_set_overlay, 0, 0); sendmessage (CTX-> hwnd, wm_cap_set_preview, 0, 0); // sets the video data receiving function. The video obtained by VFW is in its window thread. videostream_cb // is called every time a frame of data is obtained, therefore, it also executes ret = sendmessage (CTX-> hwnd, wm_cap_set_callback_videostream, 0, (lparam) videostream_cb); If (! RET) {av_log (S, av_log_error, "cocould not set video stream callback. \ n "); goto fail;} // Save the avformatcontext in the userdata area of the window, in order to obtain the avformatcontext object setwindowlongptr (CTX-> hwnd, gwlp_userdata, (long_ptr) S) in later response to the // VFW window message ); // create a stream object and add it to the avformatcontext object. St = av_new_stream (S, 0); If (! St) {vfw_read_close (s); Return averror (enomem);}/* Set video format * // first obtain the size of the Video Format Structure bisize = sendmessage (CTX-> hwnd, wm_cap_get_videoformat, 0, 0); If (! Bisize) goto fail; // allocate memory Bi = av_malloc (bisize) for storing the video format structure; If (! Bi) {vfw_read_close (s); Return averror (enomem) ;}// this is the real Video Format Structure ret = sendmessage (CTX-> hwnd, wm_cap_get_videoformat, bisize, (lparam) Bi); If (! RET) goto fail; // display dump_bih (S, & bi-> bmiheader) to the user; // analyze the frame rate, CTX-> framerate is the ret = av_parse_video_rate (& framerate_q, CTX-> framerate) obtained from the command line; If (Ret <0) {av_log (S, av_log_error, "cocould not parse framerate '% s '. \ n ", CTX-> framerate); goto fail;} // analyzes the video size, which is also obtained from the command line and is the user-requested if (CTX-> video_size) {ret = av_parse_video_size (& bi-> bmiheader. biwidth, & bi-> bmiheader. biheight, CTX-> video_size); If (Ret <0) {av_log (S, Av_log_error, "couldn't parse video size. \ n "); goto fail ;}// the parameters in video foramt are replaced by user input and set to ret = sendmessage (CTX-> hwnd, wm_cap_set_videoformat, bisize, (lparam) Bi); If (! RET) {av_log (S, av_log_error, "cocould not set video format. \ n"); goto fail;} // whether the data is compressed. VFW supports MJPEG encoding. Bicompression = Bi-> bmiheader. bicompression; bibitcount = Bi-> bmiheader. bibitcount;/* set sequence setup * // obtain the stream parameter ret = sendmessage (CTX-> hwnd, wm_cap_get_sequence_setup, sizeof (cparms), (lparam) & cparms); If (! RET) goto fail; // displays the user's dump_captureparms (S, & cparms); // sets the number of streams we want to set cparms. fyield = 1; // spawn a background threadcparms. dwrequestmicrosecperframe = (framerate_q.den * 1000000)/framerate_q.num; // duration of a frame? Right? Cparms. fabortleftmouse = 0; cparms. fabortrightmouse = 0; cparms. fcaptureaudio = 0; cparms. vkeyabort = 0; // set back ret = sendmessage (CTX-> hwnd, wm_cap_set_sequence_setup, sizeof (cparms), (lparam) & cparms); If (! RET) goto fail; // set the parameters in libav, which must be consistent with those in VFW. Codec = ST-> codec; codec-> time_base = (avrational) {framerate_q.den, framerate_q.num}; codec-> codec_type = avmedia_type_video; codec-> width = Bi-> bmiheader. biwidth; codec-> Height = Bi-> bmiheader. biheight; codec-> pix_fmt = vfw_pixfmt (bicompression, bibitcount); If (codec-> pix_fmt = pix_fmt_none) {// if no pix FMT is found, it indicates a compression format, the ID of the encoder is analyzed. Codec-> codec_id = vfw_codecid (bicompression); If (codec-> codec_id = codec_id_none) {// The encoder in this encoding format cannot be obtained. If you cannot fix it, you can only quit. Av_log (S, av_log_error, "unknown compression type. "" Please report verbose (-V 9) debug information. \ n "); vfw_read_close (s); Return averror_patchwelcome;} codec-> bits_per_coded_sample = bibitcount;} else {// If the pix fmt is found, it indicates an unencoded format, that is rawvideo. Codec-> codec_id = codec_id_rawvideo; If (bicompression = bi_rgb) {codec-> bits_per_coded_sample = bibitcount; codec-> extradata = av_malloc (9 + segments ); if (codec-> extradata) {codec-> extradata_size = 9; memcpy (codec-> extradata, "bottomup", 9) ;}} av_freep (& BI ); // set the parameter to the timestamp of each frame. 32 indicates that the timestamp is 32 bits, and 1 and 1000 indicate that the time is 1/1000 S. Av_set_pts_info (St, 32, 1, 1000); // creates mutex. videostream_cb runs in the VFW window, while read_packet runs in the FFMPEG thread, // synchronous protection is required for some things. CTX-> mutex = createmutex (null, 0, null); If (! CTX-> mutex) {av_log (S, av_log_error, "cocould not create mutex. \ n "); goto fail;} // create an event to notify the read thread of the write thread. When the data arrives, the read thread without it will be idling. CTX-> event = createevent (null, 1, 0, null); If (! CTX-> event) {av_log (S, av_log_error, "cocould not create event. \ n"); goto fail;} // start to capture the video. Does nofile set to not save the file? Right? Ret = sendmessage (CTX-> hwnd, wm_cap_sequence_nofile, 0, 0); If (! RET) {av_log (S, av_log_error, "cocould not start capture sequence. \ n "); goto fail;} return 0; fail: av_freep (& BI); vfw_read_close (s); Return averror (EIO );}
Let's look at the function for reading a frame.
Static int vfw_read_packet (avformatcontext * s, avpacket * Pkt) {struct vfw_ctx * CTX = s-> priv_data; avpacketlist * pktl = NULL; while (! Pktl) {// wait for the VFW thread to insert PKG into the pkt1 team instance. The Pkt here is a frame. Waitforsingleobject (CTX-> mutex, infinite); // we get a frame pktl = CTX-> pktl from the queue; If (CTX-> pktl) {* Pkt = CTX-> pktl-> Pkt; CTX-> pktl = CTX-> pktl-> next; av_free (pktl);} resetevent (CTX-> event ); releasemutex (CTX-> mutex); If (! Pktl) {If (S-> flags & avfmt_flag_nonblock) {return averror (eagain);} else {// if the frame queue is empty, no data is available. Wait for the VFW thread (videostream_cb) insert a new frame waitforsingleobject (CTX-> event, infinite) ;}}// to the queue and return it to the caller. CTX-> curbufsize-= Pkt-> size; return Pkt-> size ;}
VFW callback function:
// This function is executed in the window thread. The VFW captures a frame and calls the static lresult callback videostream_cb (hwnd, lpvideohdr vdhdr) {avformatcontext * s; struct vfw_ctx * CTX; avpacketlist ** ppktl, * pktl_next; // get the avformatcontexts = (avformatcontext *) getwindowlongptr (hwnd, gwlp_userdata) saved in the window ); // obtain private data CTX = s-> priv_data; dump_videohdr (S, vdhdr); // can we discard this frame? If (shall_we_drop (s) return false; // wait for the FFMPEG thread to read the frame queue waitforsingleobject (CTX-> mutex, infinite); // assign a PKG, used to store this frame pktl_next = av_mallocz (sizeof (avpacketlist); If (! Pktl_next) goto fail; If (av_new_packet (& pktl_next-> Pkt, vdhdr-> dwbytesused) <0) {av_free (pktl_next); goto fail ;} // timestamp pktl_next-> Pkt. PTS = vdhdr-> dwtimecaptured; memcpy (pktl_next-> Pkt. data, vdhdr-> lpdata, vdhdr-> dwbytesused); // Insert the packet into the queue for (ppktl = & CTX-> pktl; * ppktl; ppktl = & (* ppktl) -> next); * ppktl = pktl_next; CTX-> curbufsize + = vdhdr-> dwbytesused; setevent (CTX-> event); // sets the event so that read_packet can operate the queue. Because the queue is not empty, releasemutex (CTX-> mutex); Return true; fail: releasemutex (CTX-> mutex); Return false ;}