From 08828ef60973fbfe5c8160d6c2d5ce2648ff941d Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Mon, 2 Sep 2013 00:09:59 +0100 Subject: [PATCH 1/7] Return time stamp and sequence number with video frame --- v4l2capture.c | 51 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index dabd38c..e632b39 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -377,7 +377,7 @@ static PyObject *Video_device_queue_all_buffers(Video_device *self) Py_RETURN_NONE; } -static PyObject *Video_device_read_internal(Video_device *self, int queue) +static PyObject *Video_device_read_internal(Video_device *self, int queue, int return_timestamp) { if(!self->buffers) { @@ -444,22 +444,47 @@ static PyObject *Video_device_read_internal(Video_device *self, int queue) #undef CLAMP #endif + PyObject *out = result; + + if(return_timestamp) + { + out = PyTuple_New(4); + PyTuple_SetItem(out, 0, result); + PyTuple_SetItem(out, 1, PyInt_FromLong(buffer.timestamp.tv_sec)); + PyTuple_SetItem(out, 2, PyInt_FromLong(buffer.timestamp.tv_usec)); + PyTuple_SetItem(out, 3, PyInt_FromLong(buffer.sequence)); + } + if(queue && my_ioctl(self->fd, VIDIOC_QBUF, &buffer)) { return NULL; } - return result; + return out; } -static PyObject *Video_device_read(Video_device *self) +static PyObject *Video_device_read(Video_device *self, PyObject *args) { - return Video_device_read_internal(self, 0); + int return_timestamp=0; + + if(!PyArg_ParseTuple(args, "|i", &return_timestamp)) + { + return NULL; + } + + return Video_device_read_internal(self, 0, return_timestamp); } -static PyObject *Video_device_read_and_queue(Video_device *self) +static PyObject *Video_device_read_and_queue(Video_device *self, PyObject *args) { - return Video_device_read_internal(self, 1); + int return_timestamp=0; + + if(!PyArg_ParseTuple(args, "|i", &return_timestamp)) + { + return NULL; + } + + return Video_device_read_internal(self, 1, return_timestamp); } static PyMethodDef Video_device_methods[] = { @@ -499,14 +524,16 @@ static PyMethodDef Video_device_methods[] = { METH_NOARGS, "queue_all_buffers()\n\n" "Let the video device fill all buffers created."}, - {"read", (PyCFunction)Video_device_read, METH_NOARGS, - "read() -> string\n\n" + {"read", (PyCFunction)Video_device_read, METH_VARARGS, + "read(get_timestamp) -> string or tuple\n\n" "Reads image data from a buffer that has been filled by the video " - "device. The image data is in RGB och YUV420 format as decided by " + "device. The image data is in RGB or YUV420 format as decided by " "'set_format'. The buffer is removed from the queue. Fails if no buffer " - "is filled. Use select.select to check for filled buffers."}, - {"read_and_queue", (PyCFunction)Video_device_read_and_queue, METH_NOARGS, - "read_and_queue()\n\n" + "is filled. Use select.select to check for filled buffers. If " + "get_timestamp is true, a tuple is turned containing (sec, microsec, " + "sequence number)"}, + {"read_and_queue", (PyCFunction)Video_device_read_and_queue, METH_VARARGS, + "read_and_queue(get_timestamp)\n\n" "Same as 'read', but adds the buffer back to the queue so the video " "device can fill it again."}, {NULL} From b6ba872a7907783c0431c71e224382a8ba1c52a1 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Thu, 12 Sep 2013 20:01:18 +0100 Subject: [PATCH 2/7] Support more pixel formats --- v4l2capture.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index e632b39..4f66cd7 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -15,6 +15,7 @@ #include #include #include +#include #ifdef USE_LIBV4L #include @@ -205,9 +206,9 @@ static PyObject *Video_device_set_format(Video_device *self, PyObject *args) { int size_x; int size_y; - int yuv420 = 0; + const char *fmt = NULL; - if(!PyArg_ParseTuple(args, "ii|i", &size_x, &size_y, &yuv420)) + if(!PyArg_ParseTuple(args, "ii|s", &size_x, &size_y, &fmt)) { return NULL; } @@ -217,12 +218,17 @@ static PyObject *Video_device_set_format(Video_device *self, PyObject *args) format.fmt.pix.width = size_x; format.fmt.pix.height = size_y; #ifdef USE_LIBV4L - format.fmt.pix.pixelformat = - yuv420 ? V4L2_PIX_FMT_YUV420 : V4L2_PIX_FMT_RGB24; + format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + if(fmt != NULL && strcmp(fmt, "MJPEG")==0) + format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; + if(fmt != NULL && strcmp(fmt, "RGB24")==0) + format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + if(fmt != NULL && strcmp(fmt, "YUV420")==0) + format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; #else format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; #endif - format.fmt.pix.field = V4L2_FIELD_INTERLACED; + format.fmt.pix.field = V4L2_FIELD_NONE; format.fmt.pix.bytesperline = 0; if(my_ioctl(self->fd, VIDIOC_S_FMT, &format)) From 3203f703d7b4b659140116ffc9f846c2d03311ce Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 13 Sep 2013 14:43:28 +0100 Subject: [PATCH 3/7] Update docstrings --- v4l2capture.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index 4f66cd7..86838d5 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -507,11 +507,10 @@ static PyMethodDef Video_device_methods[] = { "set containing strings identifying the capabilities of the video " "device."}, {"set_format", (PyCFunction)Video_device_set_format, METH_VARARGS, - "set_format(size_x, size_y, yuv420 = 0) -> size_x, size_y\n\n" + "set_format(size_x, size_y, pixel_format='RGB24') -> size_x, size_y\n\n" "Request the video device to set image size and format. The device may " "choose another size than requested and will return its choice. The " - "image format will be RGB24 if yuv420 is false (default) or YUV420 if " - "yuv420 is true."}, + "pixel format may be either RGB24, YUV420 or MJPEG."}, {"set_fps", (PyCFunction)Video_device_set_fps, METH_VARARGS, "set_fps(fps) -> fps \n\n" "Request the video device to set frame per seconds.The device may " @@ -533,7 +532,7 @@ static PyMethodDef Video_device_methods[] = { {"read", (PyCFunction)Video_device_read, METH_VARARGS, "read(get_timestamp) -> string or tuple\n\n" "Reads image data from a buffer that has been filled by the video " - "device. The image data is in RGB or YUV420 format as decided by " + "device. The image data is in RGB24, YUV420 or MJPEG format as decided by " "'set_format'. The buffer is removed from the queue. Fails if no buffer " "is filled. Use select.select to check for filled buffers. If " "get_timestamp is true, a tuple is turned containing (sec, microsec, " From 057ca07598a4e314f180232141964bbe9b3eb3c0 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Thu, 17 Oct 2013 21:44:29 +0100 Subject: [PATCH 4/7] Create function to get current format --- v4l2capture.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/v4l2capture.c b/v4l2capture.c index 86838d5..7f376e4 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -16,6 +16,7 @@ #include #include #include +#include //Only used for debugging #ifdef USE_LIBV4L #include @@ -257,6 +258,19 @@ static PyObject *Video_device_set_fps(Video_device *self, PyObject *args) return Py_BuildValue("i",setfps.parm.capture.timeperframe.denominator); } +static PyObject *Video_device_get_format(Video_device *self) +{ + + struct v4l2_format format; + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if(my_ioctl(self->fd, VIDIOC_G_FMT, &format)) + { + return NULL; + } + return Py_BuildValue("ii", format.fmt.pix.width, format.fmt.pix.height); + +} + static PyObject *Video_device_start(Video_device *self) { ASSERT_OPEN; @@ -511,6 +525,8 @@ static PyMethodDef Video_device_methods[] = { "Request the video device to set image size and format. The device may " "choose another size than requested and will return its choice. The " "pixel format may be either RGB24, YUV420 or MJPEG."}, + {"get_format", (PyCFunction)Video_device_get_format, METH_NOARGS, + "get_format() -> size_x, size_y\n\n"}, {"set_fps", (PyCFunction)Video_device_set_fps, METH_VARARGS, "set_fps(fps) -> fps \n\n" "Request the video device to set frame per seconds.The device may " From c1b5bd26a7ea39ffa37fce1747ea1017c120bfdf Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Thu, 17 Oct 2013 21:54:33 +0100 Subject: [PATCH 5/7] Return pixel format information --- v4l2capture.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/v4l2capture.c b/v4l2capture.c index 7f376e4..6efb090 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -267,7 +267,32 @@ static PyObject *Video_device_get_format(Video_device *self) { return NULL; } - return Py_BuildValue("ii", format.fmt.pix.width, format.fmt.pix.height); + + PyObject *out = PyTuple_New(3); + PyTuple_SetItem(out, 0, PyInt_FromLong(format.fmt.pix.width)); + PyTuple_SetItem(out, 1, PyInt_FromLong(format.fmt.pix.height)); + + PyObject *pixFormatStr = NULL; + switch(format.fmt.pix.pixelformat) + { + case V4L2_PIX_FMT_MJPEG: + pixFormatStr = PyString_FromString("MJPEG"); + break; + case V4L2_PIX_FMT_RGB24: + pixFormatStr = PyString_FromString("RGB24"); + break; + case V4L2_PIX_FMT_YUV420: + pixFormatStr = PyString_FromString("YUV420"); + break; + case V4L2_PIX_FMT_YUYV: + pixFormatStr = PyString_FromString("YUYV"); + break; + default: + pixFormatStr = PyString_FromString("Unknown"); + break; + } + PyTuple_SetItem(out, 2, pixFormatStr); + return out; } From c5253ad36e22214241f2724831a776b627eefd5d Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 18 Oct 2013 02:58:17 +0100 Subject: [PATCH 6/7] Working toward huffman table insert code in c --- v4l2capture.c | 228 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 1 deletion(-) diff --git a/v4l2capture.c b/v4l2capture.c index 6efb090..690d740 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -532,6 +532,231 @@ static PyObject *Video_device_read_and_queue(Video_device *self, PyObject *args) return Video_device_read_internal(self, 1, return_timestamp); } +// ********************************************************************* + +#define HUFFMAN_SEGMENT_LEN 420 + +const char huffmanSegment[HUFFMAN_SEGMENT_LEN+1] = + "\xFF\xC4\x01\xA2\x00\x00\x01\x05\x01\x01\x01\x01" + "\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02" + "\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x01\x00\x03" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00" + "\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" + "\x0A\x0B\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05" + "\x05\x04\x04\x00\x00\x01\x7D\x01\x02\x03\x00\x04" + "\x11\x05\x12\x21\x31\x41\x06\x13\x51\x61\x07\x22" + "\x71\x14\x32\x81\x91\xA1\x08\x23\x42\xB1\xC1\x15" + "\x52\xD1\xF0\x24\x33\x62\x72\x82\x09\x0A\x16\x17" + "\x18\x19\x1A\x25\x26\x27\x28\x29\x2A\x34\x35\x36" + "\x37\x38\x39\x3A\x43\x44\x45\x46\x47\x48\x49\x4A" + "\x53\x54\x55\x56\x57\x58\x59\x5A\x63\x64\x65\x66" + "\x67\x68\x69\x6A\x73\x74\x75\x76\x77\x78\x79\x7A" + "\x83\x84\x85\x86\x87\x88\x89\x8A\x92\x93\x94\x95" + "\x96\x97\x98\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7\xA8" + "\xA9\xAA\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA\xC2" + "\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4\xD5" + "\xD6\xD7\xD8\xD9\xDA\xE1\xE2\xE3\xE4\xE5\xE6\xE7" + "\xE8\xE9\xEA\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9" + "\xFA\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05" + "\x04\x04\x00\x01\x02\x77\x00\x01\x02\x03\x11\x04" + "\x05\x21\x31\x06\x12\x41\x51\x07\x61\x71\x13\x22" + "\x32\x81\x08\x14\x42\x91\xA1\xB1\xC1\x09\x23\x33" + "\x52\xF0\x15\x62\x72\xD1\x0A\x16\x24\x34\xE1\x25" + "\xF1\x17\x18\x19\x1A\x26\x27\x28\x29\x2A\x35\x36" + "\x37\x38\x39\x3A\x43\x44\x45\x46\x47\x48\x49\x4A" + "\x53\x54\x55\x56\x57\x58\x59\x5A\x63\x64\x65\x66" + "\x67\x68\x69\x6A\x73\x74\x75\x76\x77\x78\x79\x7A" + "\x82\x83\x84\x85\x86\x87\x88\x89\x8A\x92\x93\x94" + "\x95\x96\x97\x98\x99\x9A\xA2\xA3\xA4\xA5\xA6\xA7" + "\xA8\xA9\xAA\xB2\xB3\xB4\xB5\xB6\xB7\xB8\xB9\xBA" + "\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xD2\xD3\xD4" + "\xD5\xD6\xD7\xD8\xD9\xDA\xE2\xE3\xE4\xE5\xE6\xE7" + "\xE8\xE9\xEA\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9\xFA"; + +int ReadJpegFrame(const unsigned char *data, unsigned offset, const unsigned char **twoBytesOut, unsigned *frameStartPosOut, unsigned *cursorOut) +{ + //Based on http://www.gdcl.co.uk/2013/05/02/Motion-JPEG.html + //and https://en.wikipedia.org/wiki/JPEG + + *twoBytesOut = NULL; + *frameStartPosOut = 0; + *cursorOut = 0; + unsigned cursor = offset; + //Check frame start + unsigned frameStartPos = offset; + const unsigned char *twoBytes = &data[cursor]; + + if (twoBytes[0] != 0xff) + { + //print "Error: found header", map(hex,twoBytes),"at position",cursor + return 0; + } + + cursor = 2 + cursor; + + //Handle padding + int paddingByte = (twoBytes[0] == 0xff && twoBytes[1] == 0xff); + if(paddingByte) + { + *twoBytesOut = twoBytes; + *frameStartPosOut = frameStartPos; + *cursorOut = cursor; + return 1; + } + + //Structure markers with 2 byte length + int markHeader = (twoBytes[0] == 0xff && twoBytes[1] >= 0xd0 && twoBytes[1] <= 0xd9); + if (markHeader) + { + *twoBytesOut = twoBytes; + *frameStartPosOut = frameStartPos; + *cursorOut = cursor; + return 1; + } + + //Determine length of compressed (entropy) data + int compressedDataStart = (twoBytes[0] == 0xff && twoBytes[1] == 0xda); + if (compressedDataStart) + { + unsigned sosLength = ((data[cursor] << 8) + data[cursor+1]); + cursor += sosLength; + + //Seek through frame + int run = 1; + while(run) + { + unsigned char byte = data[cursor]; + cursor += 1; + + if(byte == 0xff) + { + unsigned char byte2 = data[cursor]; + cursor += 1; + if(byte2 != 0x00) + { + if(byte2 >= 0xd0 && byte2 <= 0xd8) + { + //Found restart structure + //print hex(byte), hex(byte2) + } + else + { + //End of frame + run = 0; + cursor -= 2; + } + } + else + { + //Add escaped 0xff value in entropy data + } + } + else + { + + } + } + + *twoBytesOut = twoBytes; + *frameStartPosOut = frameStartPos; + *cursorOut = cursor; + return 1; + } + + //More cursor for all other segment types + unsigned segLength = (data[cursor] << 8) + data[cursor+1]; + cursor += segLength; + *twoBytesOut = twoBytes; + *frameStartPosOut = frameStartPos; + *cursorOut = cursor; + return 1; +} + +static PyObject *InsertHuffmanTable(PyObject *self, PyObject *args) +{ + /* This converts an MJPEG frame into a standard JPEG binary + MJPEG images omit the huffman table if the standard table + is used. If it is missing, this function adds the table + into the file structure. */ + + if(PyTuple_Size(args) < 1) + { + PyErr_BadArgument(); + PyErr_Format(PyExc_TypeError, "Function requires 1 argument"); + return NULL; + } + + PyObject *inBuffer = PyTuple_GetItem(args, 0); + + if(!PyString_Check(inBuffer)) + { + PyErr_BadArgument(); + PyErr_Format(PyExc_TypeError, "Argument 1 must be a string."); + //PyObject* type = PyObject_Type(inBuffer); + //PyObject_Print(type, stdout, Py_PRINT_RAW); + //Py_CLEAR(type); + + return NULL; + } + + int parsing = 1; + int frameStartPos = 0; + int huffFound = 0; + unsigned char* inBufferPtr = PyString_AsString(inBuffer); + Py_ssize_t inBufferLen = PyString_Size(inBuffer); + + PyObject *outBuffer = PyString_FromString(""); + _PyString_Resize(&outBuffer, inBufferLen + HUFFMAN_SEGMENT_LEN); + + while(parsing) + { + //Check if we should stop + if (frameStartPos >= inBufferLen) + { + parsing = 0; + continue; + } + + //Read the next segment + const unsigned char *twoBytes = NULL; + unsigned frameStartPos=0, frameEndPos=0; + int ok = ReadJpegFrame(inBufferPtr, frameStartPos, &twoBytes, &frameStartPos, &frameEndPos); + //if(verbose) + // print map(hex, twoBytes), frameStartPos, frameEndPos; + + //Stop if there is a serious error + if(!ok) + { + parsing = 0; + continue; + } + + //Check if this segment is the compressed data + if(twoBytes[0] == 0xff && twoBytes[1] == 0xda && !huffFound) + { + PyObject *substr = PyString_FromStringAndSize(huffmanSegment, HUFFMAN_SEGMENT_LEN); + PyFile_WriteObject(substr, outBuffer, Py_PRINT_RAW); + Py_CLEAR(substr); + } + + //Check the type of frame + if(twoBytes[0] == 0xff && twoBytes[1] == 0xc4) + huffFound = 1; + + //Write current structure to output + PyObject *substr = PyString_FromStringAndSize(&inBufferPtr[frameStartPos], frameEndPos - frameStartPos); + PyFile_WriteObject(substr, outBuffer, Py_PRINT_RAW); + Py_CLEAR(substr); + + //Move cursor + frameStartPos = frameEndPos; + } + + return outBuffer; +} + +// ********************************************************************* + static PyMethodDef Video_device_methods[] = { {"close", (PyCFunction)Video_device_close, METH_NOARGS, "close()\n\n" @@ -597,7 +822,8 @@ static PyTypeObject Video_device_type = { }; static PyMethodDef module_methods[] = { - {NULL} + { "InsertHuffmanTable", (PyCFunction)InsertHuffmanTable, METH_VARARGS, NULL }, + { NULL, NULL, 0, NULL } }; PyMODINIT_FUNC initv4l2capture(void) From 9402b47f07b6a60f1c38d45340a8c0ff4baaf360 Mon Sep 17 00:00:00 2001 From: Tim Sheerman-Chase Date: Fri, 18 Oct 2013 03:28:49 +0100 Subject: [PATCH 7/7] Huffman table insert now works --- v4l2capture.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/v4l2capture.c b/v4l2capture.c index 690d740..791868f 100644 --- a/v4l2capture.c +++ b/v4l2capture.c @@ -2,6 +2,7 @@ // Python extension to capture video with video4linux2 // // 2009, 2010, 2011 Fredrik Portstrom +// 2013, Tim Sheerman-Chase // // I, the copyright holder of this file, hereby release it into the // public domain. This applies worldwide. In case this is not legally @@ -16,7 +17,6 @@ #include #include #include -#include //Only used for debugging #ifdef USE_LIBV4L #include @@ -700,13 +700,12 @@ static PyObject *InsertHuffmanTable(PyObject *self, PyObject *args) } int parsing = 1; - int frameStartPos = 0; + unsigned frameStartPos = 0; int huffFound = 0; - unsigned char* inBufferPtr = PyString_AsString(inBuffer); + unsigned char* inBufferPtr = (unsigned char*)PyString_AsString(inBuffer); Py_ssize_t inBufferLen = PyString_Size(inBuffer); PyObject *outBuffer = PyString_FromString(""); - _PyString_Resize(&outBuffer, inBufferLen + HUFFMAN_SEGMENT_LEN); while(parsing) { @@ -719,8 +718,10 @@ static PyObject *InsertHuffmanTable(PyObject *self, PyObject *args) //Read the next segment const unsigned char *twoBytes = NULL; - unsigned frameStartPos=0, frameEndPos=0; + unsigned frameEndPos=0; + int ok = ReadJpegFrame(inBufferPtr, frameStartPos, &twoBytes, &frameStartPos, &frameEndPos); + //if(verbose) // print map(hex, twoBytes), frameStartPos, frameEndPos; @@ -735,8 +736,7 @@ static PyObject *InsertHuffmanTable(PyObject *self, PyObject *args) if(twoBytes[0] == 0xff && twoBytes[1] == 0xda && !huffFound) { PyObject *substr = PyString_FromStringAndSize(huffmanSegment, HUFFMAN_SEGMENT_LEN); - PyFile_WriteObject(substr, outBuffer, Py_PRINT_RAW); - Py_CLEAR(substr); + PyString_ConcatAndDel(&outBuffer, substr); } //Check the type of frame @@ -744,14 +744,13 @@ static PyObject *InsertHuffmanTable(PyObject *self, PyObject *args) huffFound = 1; //Write current structure to output - PyObject *substr = PyString_FromStringAndSize(&inBufferPtr[frameStartPos], frameEndPos - frameStartPos); - PyFile_WriteObject(substr, outBuffer, Py_PRINT_RAW); - Py_CLEAR(substr); + PyObject *substr = PyString_FromStringAndSize((char *)&inBufferPtr[frameStartPos], frameEndPos - frameStartPos); + PyString_ConcatAndDel(&outBuffer, substr); //Move cursor frameStartPos = frameEndPos; } - + return outBuffer; }