/* * Copyright (C) 2005-2006 Micronas USA Inc. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and the associated README documentation file (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* May 27, 2005 * A-V Sync by Timo Pylvanainen, tpyl+nosa at iki fi */ /* July 23, 2006 * Playable AVI while recording * by Francois Beerten, avrecord dot 10 dot fb at spamgourmet dot com */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "go7007.h" /* Note: little-endian */ #define PUT_16(p,v) ((p)[0]=(v)&0xff,(p)[1]=((v)>>8)&0xff) #define PUT_32(p,v) ((p)[0]=(v)&0xff,(p)[1]=((v)>>8)&0xff,(p)[2]=((v)>>16)&0xff,(p)[3]=((v)>>24)&0xff) #define FOURCC(c) (((c)[3]<<24)|((c)[2]<<16)|((c)[1]<<8)|(c)[0]) #define MAX_BUFFERS 32 #define AUDIO_BUF_LEN (256*1024) #define MIN_SYNC_STEP 16 #ifndef V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC #define V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC 2 #endif char *vdevice = NULL, *adevice = NULL, *avibase = NULL; char avifile[PATH_MAX]; int vidfd = -1, audfd = -1, avifd = -1, idxfd = -1; int buf_count = 0; unsigned long long int avi_frame_count = 0; unsigned long long int total_frames = 0; int total_av_corr = 0; double av_offset = 0; double fps=29.97; int duration = -1; unsigned int sequence; int frame_limit = -1; int max_frame_length = 0; unsigned long long int max_file_size = 0; unsigned long long int vbytes = 0, abytes = 0; unsigned char audio_buffer[AUDIO_BUF_LEN]; unsigned char audio_sync_buffer[AUDIO_BUF_LEN]; int audio_len = 0; int nowrite = 0; int probe = 0; int verbose = 0; int input = 0; int width = 0, height = 0; struct v4l2_fract frameperiod; enum { FMT_MJPEG, FMT_MPEG1, FMT_MPEG2, FMT_MPEG4, } format = FMT_MPEG4; int interrupted = 0; unsigned char *buffers[MAX_BUFFERS]; void usage(char *progname) { printf( "gorecord captures video and audio from a WIS GO7007 video encoder and\n" "writes it to an AVI file. Capturing continues until the specified frame\n" "limit has been reached or the maximum AVI file size is exceeded.\n\n" "Usage: gorecord [OPTION]... -frames []\n\n" "" "Control options:\n" " -verbose Verbosely describe all operations\n" " -duration Stop capturing after seconds\n" " -frames Stop capturing after video frames\n" " -maxsize Stop capturing after Megabytes (2^20 bytes)\n" " -noaudio Do not capture audio; only video\n" " -nowrite Do not write captured video/audio to a file\n" " -vdevice Explicitly specify the V4L2 device to use\n" " -adevice Explicitly specify the OSS device to use\n" "" "Other Options:\n" " Use \"%%d\" in the filename in conjunction with the -maxsize option to\n" " create a new file every Megabytes. eg. gorecord filename-\"%%d\".avi\n"); exit(1); } void find_devices(int noaudio) { struct stat si; struct dirent *ent; int i, minor = -1; DIR *dir; FILE *file; char line[128], sympath[PATH_MAX], canonpath[PATH_MAX], gopath[PATH_MAX]; static char vdev[PATH_MAX], adev[PATH_MAX]; /* Make sure sysfs is mounted and the driver is loaded */ if (stat("/sys/bus/usb/drivers", &si) < 0) { fprintf(stderr, "Unable to read /sys/bus/usb/drivers: %s\n", strerror(errno)); fprintf(stderr, "Is sysfs mounted on /sys?\n"); exit(1); } if (stat("/sys/bus/usb/drivers/go7007", &si) < 0) { fprintf(stderr, "Unable to read /sys/bus/usb/drivers/go7007: " "%s\n", strerror(errno)); fprintf(stderr, "Is the go7007-usb kernel module loaded?\n"); exit(1); } /* Find a Video4Linux device associated with the go7007 driver */ for (i = 0; i < 20; ++i) { sprintf(sympath, "/sys/class/video4linux/video%d/device/driver", i); if (realpath(sympath, canonpath) == NULL) continue; if (!strcmp(strrchr(canonpath, '/') + 1, "go7007")) break; } sprintf(sympath, "/sys/class/video4linux/video%d/device", i); if (i == 20 || realpath(sympath, gopath) == NULL) { fprintf(stderr, "Driver loaded but no GO7007 devices found.\n"); fprintf(stderr, "Is the device connected properly?\n"); exit(1); } sprintf(vdev, "/dev/video%d", i); vdevice = vdev; fprintf(stderr, "%s is a GO7007 device at USB address %s\n", vdev, strrchr(gopath, '/') + 1); if (noaudio) return; /* Find the ALSA device associated with this USB address */ fprintf(stderr, "Attempting to determine audio device..."); for (i = 0; i < 20; ++i) { sprintf(sympath, "/sys/class/sound/pcmC%dD0c/device/device", i); if (realpath(sympath, canonpath) == NULL) continue; if (!strcmp(gopath, canonpath)) break; } if (i == 20) { fprintf(stderr, "\nUnable to find associated ALSA device node\n"); exit(1); } /* Find the OSS emulation minor number for this ALSA device */ file = fopen("/proc/asound/oss/devices", "r"); if (file == NULL) { fprintf(stderr, "\nUnable to open /proc/asound/oss/devices: %s\n", strerror(errno)); fprintf(stderr, "Is the snd_pcm_oss module loaded?\n"); exit(1); } while ((fgets(line, sizeof(line), file)) != NULL) { unsigned int n; int m; char *c; if ((c = strrchr(line, ':')) == NULL || strcmp(c, ": digital audio\n") || sscanf(line, "%d: [%u-%*u]:", &m, &n) != 2) continue; if (n == i) { minor = m; break; } } fclose(file); if (minor < 0) { fprintf(stderr, "\nUnable to find emulated OSS device node\n"); exit(1); } dir = opendir("/sys/class/sound"); if (dir == NULL) { fprintf(stderr, "\nUnable to read /sys/class/sound: %s\n", strerror(errno)); exit(1); } while ((ent = readdir(dir)) != NULL) { int m = -1; if (strncmp(ent->d_name, "dsp", 3)) continue; sprintf(adev, "/sys/class/sound/%s/dev", ent->d_name); file = fopen(adev, "r"); if (file == NULL) continue; if ((fgets(line, sizeof(line), file)) != NULL) sscanf(line, "%*d:%d\n", &m); fclose(file); if (m == minor) break; } if (ent == NULL) { fprintf(stderr, "\nUnable to find emulated OSS device.\n"); exit(1); } sprintf(adev, "/dev/%s", ent->d_name); closedir(dir); adevice = adev; fprintf(stderr, "using audio device %s\n", adev); } void parse_opts(int argc, char **argv) { int i, noaudio = 0; for (i = 1; i < argc && argv[i][0] == '-'; ++i) { if (!strcmp(argv[i], "-help")) usage(argv[0]); else if (!strcmp(argv[i], "-nosound")) { noaudio = 1; } else if (!strcmp(argv[i], "-noaudio")) { noaudio = 1; } else if (!strcmp(argv[i], "-verbose")) { verbose = 1; } else if (!strcmp(argv[i], "-nowrite")) { nowrite = 1; } else if (i + 1 >= argc) { usage(argv[0]); /* options that take an argument go below here */ } else if (!strcmp(argv[i], "-vdevice")) { vdevice = argv[++i]; } else if (!strcmp(argv[i], "-adevice")) { adevice = argv[++i]; } else if (!strcmp(argv[i], "-frames")) { frame_limit = atoi(argv[++i]); } else if (!strcmp(argv[i], "-duration")) { duration = atoi(argv[++i]); } else if (!strcmp(argv[i], "-maxsize")) { max_file_size = atoi(argv[++i]) *1024*1024; } else { usage(argv[0]); } } if (nowrite) { if (i != argc) usage(argv[0]); } else { if (i == argc) { nowrite = 1; verbose = 1; probe = 1; avibase = NULL; } else if (i + 1 != argc) usage(argv[0]); else avibase = argv[i]; } if (noaudio) adevice = NULL; if (vdevice != NULL) { if (adevice == NULL && !noaudio) { fprintf(stderr, "If -vdevice is used, please also " "specify an audio device with -adevice or " "use -noaudio.\n"); exit(1); } } else { if (adevice != NULL) { fprintf(stderr, "If -adevice is used, please also " "specify a video device with -vdevice.\n"); exit(1); } find_devices(noaudio); } } int add_video_stream_header(unsigned char *hdr) { unsigned int video_fourcc; switch (format) { case FMT_MJPEG: video_fourcc = FOURCC("mjpg"); break; case FMT_MPEG1: video_fourcc = FOURCC("mpg1"); break; case FMT_MPEG2: video_fourcc = FOURCC("mpg2"); break; case FMT_MPEG4: video_fourcc = FOURCC("DX50"); break; default: video_fourcc = 0; break; } PUT_32(hdr, FOURCC("LIST")); PUT_32(hdr + 4, 12 - 8 + 64 + 48); PUT_32(hdr + 8, FOURCC("strl")); PUT_32(hdr + 12, FOURCC("strh")); PUT_32(hdr + 12 + 4, 64 - 8); PUT_32(hdr + 12 + 8, FOURCC("vids")); PUT_32(hdr + 12 + 12, video_fourcc); PUT_32(hdr + 12 + 28, frameperiod.numerator); PUT_32(hdr + 12 + 32, frameperiod.denominator); PUT_32(hdr + 12 + 40, avi_frame_count); PUT_32(hdr + 12 + 44, max_frame_length); PUT_16(hdr + 12 + 60, width); PUT_16(hdr + 12 + 62, height); PUT_32(hdr + 12 + 64, FOURCC("strf")); PUT_32(hdr + 12 + 64 + 4, 48 - 8); PUT_32(hdr + 12 + 64 + 8, 48 - 8); PUT_32(hdr + 12 + 64 + 12, width); PUT_32(hdr + 12 + 64 + 16, height); PUT_32(hdr + 12 + 64 + 20, 1); PUT_32(hdr + 12 + 64 + 22, 24); PUT_32(hdr + 12 + 64 + 24, video_fourcc); PUT_32(hdr + 12 + 64 + 28, width * height * 3); return 12 + 64 + 48; } int add_audio_stream_header(unsigned char *hdr) { PUT_32(hdr, FOURCC("LIST")); PUT_32(hdr + 4, 12 - 8 + 64 + 26); PUT_32(hdr + 8, FOURCC("strl")); PUT_32(hdr + 12, FOURCC("strh")); PUT_32(hdr + 12 + 4, 64 - 8); PUT_32(hdr + 12 + 8, FOURCC("auds")); PUT_32(hdr + 12 + 12, 1); PUT_32(hdr + 12 + 28, 4); PUT_32(hdr + 12 + 32, 48000 << 2); PUT_32(hdr + 12 + 40, abytes >> 2); PUT_32(hdr + 12 + 44, 48000 << 1); PUT_32(hdr + 12 + 52, 4); PUT_32(hdr + 12 + 64, FOURCC("strf")); PUT_32(hdr + 12 + 64 + 4, 26 - 8); PUT_16(hdr + 12 + 64 + 8, 1); PUT_16(hdr + 12 + 64 + 10, 2); PUT_32(hdr + 12 + 64 + 12, 48000); PUT_32(hdr + 12 + 64 + 16, 48000 << 2); PUT_16(hdr + 12 + 64 + 20, 4); PUT_16(hdr + 12 + 64 + 22, 16); return 12 + 64 + 26; } void open_avifile(void) { /* First open the temporary file we'll store the index in */ idxfd = open(avifile, O_RDWR | O_CREAT | O_TRUNC, 0666); if (idxfd < 0) { fprintf(stderr, "Unable to open %s: %s\n", avifile, strerror(errno)); exit(1); } unlink(avifile); /* Then open the real AVI destination file */ avifd = open(avifile, O_RDWR | O_CREAT | O_TRUNC, 0666); if (avifd < 0) { fprintf(stderr, "Unable to open %s: %s\n", avifile, strerror(errno)); exit(1); } int movielen, off; unsigned char hdr[1024 + 12]; memset(hdr, 0, sizeof(hdr)); PUT_32(hdr, FOURCC("RIFF")); PUT_32(hdr + 4, lseek(avifd, 0, SEEK_CUR) - 8); PUT_32(hdr + 8, FOURCC("AVI ")); PUT_32(hdr + 12, FOURCC("LIST")); PUT_32(hdr + 12 + 8, FOURCC("hdrl")); PUT_32(hdr + 12 + 12, FOURCC("avih")); PUT_32(hdr + 12 + 12 + 4, 64 - 8); /* bizarre math to do microsecond arithmetic in 32-bit ints */ /* => 1000000 * frameperiod.numerator / frameperiod.denominator */ PUT_32(hdr + 12 + 12 + 8, (frameperiod.numerator * 15625 / frameperiod.denominator) * 64 + ((frameperiod.numerator * 15625) % frameperiod.denominator) * 64 / frameperiod.denominator); PUT_32(hdr + 12 + 12 + 20, 2320); PUT_32(hdr + 12 + 12 + 24, avi_frame_count); PUT_32(hdr + 12 + 12 + 32, audfd < 0 ? 1 : 2); PUT_32(hdr + 12 + 12 + 36, 128*1024); PUT_32(hdr + 12 + 12 + 40, width); PUT_32(hdr + 12 + 12 + 44, height); off = 64; off += add_video_stream_header(hdr + 12 + 12 + off); if (audfd >= 0) off += add_audio_stream_header(hdr + 12 + 12 + off); PUT_32(hdr + 12 + 4, 12 - 8 + off); PUT_32(hdr + 12 + 12 + off, FOURCC("JUNK")); PUT_32(hdr + 12 + 12 + off + 4, 1024 - 12 - 12 - off - 8); PUT_32(hdr + 1024, FOURCC("LIST")); PUT_32(hdr + 1024 + 4, movielen - 1024 - 8); PUT_32(hdr + 1024 + 8, FOURCC("movi")); lseek(avifd, 0, SEEK_SET); write(avifd, hdr, 1024 + 12); lseek(avifd, 1024 + 12, SEEK_SET); } void open_devices(void) { vidfd = open(vdevice, O_RDWR); if (vidfd < 0) { fprintf(stderr, "Unable to open %s: %s\n", vdevice, strerror(errno)); exit(1); } if (adevice == NULL) return; audfd = open(adevice, O_RDONLY); if (audfd < 0) { fprintf(stderr, "Unable to open %s: %s\n", adevice, strerror(errno)); exit(1); } fcntl(audfd, F_SETFL, O_NONBLOCK); } void alsa_init(void) { int arg; arg = AFMT_S16_LE; if (ioctl(audfd, SNDCTL_DSP_SETFMT, &arg) < 0) { perror("SNDCTL_DSP_SETFMT"); exit(1); } arg = 48000; if (ioctl(audfd, SNDCTL_DSP_SPEED, &arg) < 0) { perror("SNDCTL_DSP_SPEED"); exit(1); } arg = 1; if (ioctl(audfd, SNDCTL_DSP_STEREO, &arg) < 0) { perror("SNDCTL_DSP_STEREO"); exit(1); } } void v4l2_init(void) { struct v4l2_capability cap; struct v4l2_streamparm parm; struct v4l2_format fmt; struct v4l2_requestbuffers req; struct v4l2_buffer buf; struct v4l2_input inp; __u32 i; /* First query the capabilities of the video capture device */ if (ioctl(vidfd, VIDIOC_QUERYCAP, &cap) < 0) { perror("VIDIOC_QUERYCAP"); exit(1); } if (verbose) { printf("\nDriver: %s\n", cap.driver); printf("Card: %s\n", cap.card); printf("Version: %u.%u.%u\n\n", (cap.version >> 16) & 0xff, (cap.version >> 8) & 0xff, cap.version & 0xff); } if (probe) return; /* Print some info about the input port */ if (ioctl(vidfd, VIDIOC_G_INPUT, &input) < 0) { fprintf(stderr, "Unable to get input port\n"); exit(1); } memset(&inp, 0, sizeof(inp)); inp.index = input; if (ioctl(vidfd, VIDIOC_ENUMINPUT, &inp) < 0) { fprintf(stderr, "Input port %d does not exist on this hardware\n", input); exit(1); } if(verbose) fprintf(stderr, "Using input port %s\n", inp.name); /* Get the format and read width and height */ memset(&fmt, 0, sizeof(fmt)); fmt.type= V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(vidfd, VIDIOC_G_FMT, &fmt) < 0) { fprintf(stderr, "Unable to get format\n"); exit(1); } width = fmt.fmt.pix.width; height = fmt.fmt.pix.height; if(fmt.fmt.pix.pixelformat==V4L2_PIX_FMT_MJPEG) format = FMT_MJPEG; else { struct v4l2_ext_control ctrl; struct v4l2_ext_controls ctrls; memset(&ctrl, 0, sizeof(ctrl)); memset(&ctrls, 0, sizeof(ctrls)); ctrl.id = V4L2_CID_MPEG_VIDEO_ENCODING; ctrls.ctrl_class = V4L2_CTRL_ID2CLASS(ctrl.id); ctrls.count = 1; ctrls.controls = &ctrl; if (ioctl(vidfd, VIDIOC_G_EXT_CTRLS, &ctrls) < 0) { fprintf(stderr, "Unable to get video encoding format\n"); exit(1); } switch (ctrl.value) { case V4L2_MPEG_VIDEO_ENCODING_MPEG_1: format = FMT_MPEG1; break; case V4L2_MPEG_VIDEO_ENCODING_MPEG_2: format = FMT_MPEG2; break; case V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC: format = FMT_MPEG4; break; default: break; } } /* Get the frame period */ memset(&parm, 0, sizeof(parm)); parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(vidfd, VIDIOC_G_PARM, &parm) < 0) { fprintf(stderr, "Unable to query the frame rate\n"); exit(1); } frameperiod = parm.parm.capture.timeperframe; if(verbose) fprintf(stderr, "Capturing video at %dx%d, %.2f FPS\n", width, height, (double)frameperiod.denominator / (double)frameperiod.numerator); /* Request that buffers be allocated for memory mapping */ memset(&req, 0, sizeof(req)); req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; req.count = MAX_BUFFERS; if (ioctl(vidfd, VIDIOC_REQBUFS, &req) < 0) { perror("VIDIOC_REQBUFS"); exit(1); } if (verbose) printf("Received %d buffers\n", req.count); buf_count = req.count; /* Map each of the buffers into this process's memory */ for (i = 0; i < buf_count; ++i) { memset(&buf, 0, sizeof(buf)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(vidfd, VIDIOC_QUERYBUF, &buf) < 0) { perror("VIDIOC_QUERYBUF"); exit(1); } buffers[buf.index] = (unsigned char *)mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, vidfd, buf.m.offset); } /* Queue all of the buffers for frame capture */ for (i = 0; i < buf_count; ++i) { memset(&buf, 0, sizeof(buf)); buf.index = i; buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; if (ioctl(vidfd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF"); exit(1); } } } void v4l2_start(void) { int arg = V4L2_BUF_TYPE_VIDEO_CAPTURE; sequence = 0; if (ioctl(vidfd, VIDIOC_STREAMON, &arg) < 0) { perror("VIDIOC_STREAMON"); exit(1); } } void v4l2_stop(void) { int arg = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(vidfd, VIDIOC_STREAMOFF, &arg) < 0) { perror("VIDIOC_STREAMOFF"); exit(1); } } void write_frame(unsigned char *data, int length, unsigned int fourcc, int key) { unsigned char hdr[16]; unsigned int offset; if (avifd < 0) return; /* Write the AVI index record */ PUT_32(hdr, fourcc); PUT_32(hdr + 4, key ? 0x10 : 0); offset = lseek(avifd, 0, SEEK_CUR); PUT_32(hdr + 8, offset - 1024 - 8); PUT_32(hdr + 12, length); write(idxfd, hdr, 16); /* Write the frame data to the AVI file */ PUT_32(hdr + 4, length); write(avifd, hdr, 8); write(avifd, data, (length + 1) & ~0x1); /* word-align with junk */ } int alsa_read(void) { int ret; ret = read(audfd, audio_buffer + audio_len, sizeof(audio_buffer) - audio_len); if (ret < 0) { if (errno == EAGAIN) return 0; perror("Unexpected error reading from audio device"); return ret; } if (interrupted) return 0; audio_len += ret; return ret; } void average16b(unsigned char* dst, unsigned char* src1, unsigned char* src2) { int16_t a,b,c; a = (((u_int16_t)*(src1+1)) << 8) | (u_int16_t)*(src1); b = (((u_int16_t)*(src2+1)) << 8) | (u_int16_t)*(src2); c = a/2+b/2 + (a&b&0x1); *dst = ((u_int16_t)c)&0xFF; *(dst+1) = (((u_int16_t)c)>>8)&0xFF; } int audio_video_sync(void) { int i=0; int j=0; int k=0; static int sync_step=0; while(i 4000 && sync_step > (int)(192000.0/fabs(av_offset)) && sync_step > MIN_SYNC_STEP ) { /* If audio lagging, i.e. too many audio bytes, and * enough audio_buffer left to average samples then do that */ if(av_offset < 0 && i < audio_len-4) { /* Merge two samples into one */ for(k=0;k<2;k++) { average16b(&audio_sync_buffer[j], &audio_buffer[i], &audio_buffer[i+4]); i+=2; j+=2; } i+=4; /* Offset now reduced by 4 bytes */ av_offset += 4; total_av_corr -= 4; /* Correction performed */ sync_step = 0; } else if(av_offset > 0 && j < AUDIO_BUF_LEN-4) { /* Audio running, i.e. too few audio bytes, then insert * samples */ for(k=0;k<2;k++) { /* Copy first words */ audio_sync_buffer[j] = audio_buffer[i]; audio_sync_buffer[j+1] = audio_buffer[i+1]; /* Create second words */ average16b(&audio_sync_buffer[j+4], &audio_buffer[i], &audio_buffer[i+4]); i+=2; j+=2; } j+=4; /* Four bytes added */ av_offset -= 4; total_av_corr += 4; /* Correction performed */ sync_step = 0; } else { /* To make sure something is always done */ for(k=0;k<4&&i= 0) { alsa_read(); if (interrupted) return 0; /* Must be careful to update av_offset before * calling audio_video_sync */ av_offset -= (double)audio_len; audio_len = audio_video_sync(); if(interrupted) return 0; write_frame(audio_sync_buffer, audio_len, FOURCC("01wb"), 1); abytes += audio_len; audio_len = 0; } /* Then write the video frame */ write_frame(buffers[buf.index], length, FOURCC("00dc"), key); /* Update global variables */ if (length > max_frame_length) max_frame_length = length; vbytes += length; /* Send the frame back to the kernel to be filled again */ if (ioctl(vidfd, VIDIOC_QBUF, &buf) < 0) { perror("VIDIOC_QBUF"); exit(1); } return length; } void avi_finish(void) { unsigned long long int filelen; int i, movielen, off; unsigned char hdr[1024 + 12]; movielen = lseek(avifd, 0, SEEK_CUR); PUT_32(hdr, FOURCC("idx1")); PUT_32(hdr + 4, lseek(idxfd, 0, SEEK_CUR)); write(avifd, hdr, 8); lseek(idxfd, 0, SEEK_SET); while ((i = read(idxfd, hdr, sizeof(hdr))) > 0) if (write(avifd, hdr, i) < 0) { perror("Unable to write index data to AVI file"); exit(1); } close(idxfd); filelen = lseek(avifd, 0, SEEK_CUR); memset(hdr, 0, sizeof(hdr)); PUT_32(hdr, FOURCC("RIFF")); PUT_32(hdr + 4, lseek(avifd, 0, SEEK_CUR) - 8); PUT_32(hdr + 8, FOURCC("AVI ")); PUT_32(hdr + 12, FOURCC("LIST")); PUT_32(hdr + 12 + 8, FOURCC("hdrl")); PUT_32(hdr + 12 + 12, FOURCC("avih")); PUT_32(hdr + 12 + 12 + 4, 64 - 8); /* bizarre math to do microsecond arithmetic in 32-bit ints */ /* => 1000000 * frameperiod.numerator / frameperiod.denominator */ PUT_32(hdr + 12 + 12 + 8, (frameperiod.numerator * 15625 / frameperiod.denominator) * 64 + ((frameperiod.numerator * 15625) % frameperiod.denominator) * 64 / frameperiod.denominator); PUT_32(hdr + 12 + 12 + 20, 2320); PUT_32(hdr + 12 + 12 + 24, avi_frame_count); PUT_32(hdr + 12 + 12 + 32, audfd < 0 ? 1 : 2); PUT_32(hdr + 12 + 12 + 36, 128*1024); PUT_32(hdr + 12 + 12 + 40, width); PUT_32(hdr + 12 + 12 + 44, height); off = 64; off += add_video_stream_header(hdr + 12 + 12 + off); if (audfd >= 0) off += add_audio_stream_header(hdr + 12 + 12 + off); PUT_32(hdr + 12 + 4, 12 - 8 + off); PUT_32(hdr + 12 + 12 + off, FOURCC("JUNK")); PUT_32(hdr + 12 + 12 + off + 4, 1024 - 12 - 12 - off - 8); PUT_32(hdr + 1024, FOURCC("LIST")); PUT_32(hdr + 1024 + 4, movielen - 1024 - 8); PUT_32(hdr + 1024 + 8, FOURCC("movi")); lseek(avifd, 0, SEEK_SET); write(avifd, hdr, 1024 + 12); close(avifd); fprintf(stderr, "\n Video data written to file: %llu bytes of ", vbytes); switch (format) { case FMT_MJPEG: fprintf(stderr, "Motion-JPEG\n"); break; case FMT_MPEG1: fprintf(stderr, "MPEG1\n"); break; case FMT_MPEG2: fprintf(stderr, "MPEG2\n"); break; case FMT_MPEG4: fprintf(stderr, "MPEG4\n"); break; } if (abytes > 0) fprintf(stderr, " Audio data written to file: %llu bytes of %s\n", abytes, "uncompressed PCM"); fprintf(stderr, " AVI file format overhead : %llu bytes\n", filelen - vbytes - abytes); fprintf(stderr, " Total file size : %llu bytes\n", filelen); fprintf(stderr, " Total A/V correction : %d bytes\n\n", total_av_corr ); } void interrupt_handler(int signal) { interrupted = 1; } int main(int argc, char **argv) { struct sigaction sa; struct timeval cur, start, mmark; unsigned long long int csec, filesize; int vframe_len; int file_count = 1, mrate = 0; unsigned long long int mcount = 0, mbytes = 0; memset(&sa, 0, sizeof(sa)); sa.sa_handler = interrupt_handler; sa.sa_flags = SA_RESTART; sigaction(SIGINT, &sa, NULL); sigaction(SIGTERM, &sa, NULL); sigaction(SIGHUP, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); memset(audio_buffer, 0, sizeof(audio_buffer)); parse_opts(argc, argv); open_devices(); v4l2_init(); if (audfd >= 0) alsa_init(); if (probe) { printf("For usage help, run `%s -help`\n", argv[0]); return 0; } if (!nowrite) { if (strstr(avibase, "%d")) sprintf(avifile, avibase, file_count); else strcpy(avifile, avibase); open_avifile(); } v4l2_start(); /* This will set the audiosync to be in line with the initial sync */ av_offset += 192000.0*2.0/fps; for (;;) { /* Retrieve a new video frame and audio frame */ vframe_len = v4l2_frame_capture(&cur); if (interrupted) break; if (vframe_len < 0) break; /* Display file length, timestamp, and frame count */ if (avifd < 0) filesize = vbytes + abytes; else filesize = lseek(avifd, 0, SEEK_CUR); ++avi_frame_count; av_offset += 192000.0/fps; if (total_frames++ == 0) mmark = start = cur; csec = 100 * (cur.tv_sec - start.tv_sec) + (cur.tv_usec - start.tv_usec) / 10000; fprintf(stderr, "\r %02llu:%02llu:%02llu.%02llu Frames: %5llu " "AVI size: %3lluMB A-V: %7.2fms", csec / 360000 % 60, csec / 6000 % 60, csec / 100 % 60, csec % 100, total_frames, filesize / 1024 / 1024, -av_offset/192); /* Calculate and display video bitrate */ if (mcount++ == 50) { csec = 100 * (cur.tv_sec - mmark.tv_sec) + (cur.tv_usec - mmark.tv_usec) / 10000; mrate = (4 * mbytes) / (5 * csec); mbytes = 0; mcount = 1; mmark = cur; } mbytes += vframe_len; if (mrate > 0) fprintf(stderr, " Video: %5dkbps", mrate); /* If we've reached the maximum AVI length, make a new file * if we were given a filename with %d, or otherwise stop * recording */ if (avifd >= 0 && filesize >= max_file_size && max_file_size > 0) { if(strstr(avibase, "%d")) { avi_finish(); ++file_count; sprintf(avifile, avibase, file_count); avi_frame_count = 0; max_frame_length = 0; abytes = 0; vbytes = 0; open_avifile(); } else { fprintf(stderr, "\nAVI file size limit reached\n"); break; } } /* If we've reached the user-specified maximum number of * frames to record, stop */ if (frame_limit >= 0 && total_frames == frame_limit) break; /* If we've reached the user-specified maximum duration, stop */ if (duration >= 0 && cur.tv_sec >= start.tv_sec + duration && cur.tv_usec >= start.tv_usec) break; /* If we've reached the max file size, stop unless "%d" is in the filename */ if (max_file_size > 0 && filesize >= max_file_size && ! strstr(avibase, "%d")) break; } fprintf(stderr, "\n"); v4l2_stop(); if (avifd >= 0) avi_finish(); return 0; };