]> git.sesse.net Git - fjl/blobdiff - bytesource.c
Add a new input source converting JPEG-format bytes into unstuffed bytes.
[fjl] / bytesource.c
diff --git a/bytesource.c b/bytesource.c
new file mode 100644 (file)
index 0000000..25cea22
--- /dev/null
@@ -0,0 +1,122 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <assert.h>
+
+#include "choice.h"
+#include "bytesource.h"
+
+#define MARKER_CHAR 0xff
+#define STUFF_MARKER 0x00
+
+void init_byte_source(struct byte_source* source, raw_input_func_t* input_func, void* userdata)
+{
+       // TODO: should this size be a different constant?
+       memset(source, 0, sizeof(*source));
+       source->bytes = (uint8_t*)malloc(BYTESOURCE_CHUNK_SIZE);
+       source->input_func = input_func;
+       source->userdata = userdata;
+}
+
+uint8_t byte_source_read_marker(struct byte_source* source)
+{
+       assert(source->bytes_available >= 2);
+       assert(source->bytes[0] == MARKER_CHAR);
+       assert(source->bytes[1] != STUFF_MARKER);
+
+       uint8_t ret = source->bytes[1];
+
+       memmove(source->bytes, source->bytes + 2, source->bytes_available - 2);
+       source->bytes_available -= 2;
+
+       return ret;
+}
+
+ssize_t byte_source_input_func(void* source, uint8_t* buf, size_t len)
+{
+       struct byte_source* src = (struct byte_source*)source;
+
+       // If there's no data in the buffer (or only a partial marker), we have
+       // to read in more from our upstream src.
+       while (src->bytes_available == 0 ||
+              (src->bytes_available == 1 && src->bytes[0] == MARKER_CHAR)) {
+               const unsigned space_left = BYTESOURCE_CHUNK_SIZE - src->bytes_available;
+               const size_t bytes_to_read = (len > space_left ? space_left : len);
+               assert(bytes_to_read <= BYTESOURCE_CHUNK_SIZE);
+               const ssize_t bytes_read =
+                       (*src->input_func)(src->userdata,
+                                             src->bytes + src->bytes_available,
+                                             bytes_to_read);
+               assert(bytes_read >= -1);
+               assert(bytes_read <= bytes_to_read);
+               
+               if (bytes_read == -1) {
+                       return -1;
+               } else if (bytes_read == 0) {
+                       if (src->bytes_available == 1) {
+                               // EOF in the middle of a marker => read error
+                               return -1;
+                       } else {
+                               assert(src->bytes_available == 0);
+                               return 0;
+                       }
+               }
+
+               src->bytes_available += bytes_read;
+       }
+       
+       // Now unstuff as much as we can. First of all, if there's a 0xFF at the
+       // end of the buffer, we don't include it this time; the unstuff function
+       // will only give us an error since it can't decide if it's a marker or
+       // a stuffed 0xFF.
+       unsigned bytes_to_unstuff = src->bytes_available;
+       bool end_marker = false;
+       assert(bytes_to_unstuff > 0);
+       if (src->bytes[bytes_to_unstuff - 1] == 0xff) {
+               --bytes_to_unstuff;
+               end_marker = true;
+       }
+
+       int unstuffed_bytes = (*unstuff_choice)(buf, src->bytes, bytes_to_unstuff);
+       assert(unstuffed_bytes != 0);
+       if (unstuffed_bytes > 0) {
+               // Fast path: No markers in the data. We can basically just
+               // return it.
+               if (end_marker) {
+                       src->bytes_available = 1;
+                       src->bytes[0] = 0xff;
+               } else {
+                       src->bytes_available = 0;
+                       src->bytes[0] = 0;
+               }
+               return unstuffed_bytes;
+       }
+
+       // Slow path: There was a marker in the data. Unstuff manually until
+       // we hit the marker, then return that.
+       assert(unstuffed_bytes == -1);
+       unsigned bytes_read;
+       unsigned bytes_written = 0;
+       for (bytes_read = 0; bytes_read < src->bytes_available; ++bytes_read) {
+               buf[bytes_written++] = src->bytes[bytes_read];
+               if (src->bytes[bytes_read] != MARKER_CHAR) {
+                       continue;
+               }
+
+               assert(bytes_read < src->bytes_available);
+               if (src->bytes[bytes_read + 1] == STUFF_MARKER) {
+                       // Skip the stuff byte.
+                       ++bytes_read;
+                       continue;
+               } else {
+                       // OK, this is our marker.
+                       break;
+               }       
+       }
+
+       memmove(src->bytes, src->bytes + bytes_read, src->bytes_available - bytes_read);
+       src->bytes_available -= bytes_read;
+       assert(bytes_written >= 1);
+       return bytes_written - 1;
+}