/* GStreamer JPEG parser test utility * Copyright (C) 2015 Tim-Philipp Müller * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include static GstBuffer *app_segments[16]; /* NULL */ static const gchar * get_marker_name (guint8 marker) { switch (marker) { case GST_JPEG_MARKER_SOF0: return "SOF (Baseline)"; case GST_JPEG_MARKER_SOF1: return "SOF (Extended Sequential, Huffman)"; case GST_JPEG_MARKER_SOF2: return "SOF (Extended Progressive, Huffman)"; case GST_JPEG_MARKER_SOF3: return "SOF (Lossless, Huffman)"; case GST_JPEG_MARKER_SOF5: return "SOF (Differential Sequential, Huffman)"; case GST_JPEG_MARKER_SOF6: return "SOF (Differential Progressive, Huffman)"; case GST_JPEG_MARKER_SOF7: return "SOF (Differential Lossless, Huffman)"; case GST_JPEG_MARKER_SOF9: return "SOF (Extended Sequential, Arithmetic)"; case GST_JPEG_MARKER_SOF10: return "SOF (Progressive, Arithmetic)"; case GST_JPEG_MARKER_SOF11: return "SOF (Lossless, Arithmetic)"; case GST_JPEG_MARKER_SOF13: return "SOF (Differential Sequential, Arithmetic)"; case GST_JPEG_MARKER_SOF14: return "SOF (Differential Progressive, Arithmetic)"; case GST_JPEG_MARKER_SOF15: return "SOF (Differential Lossless, Arithmetic)"; case GST_JPEG_MARKER_DHT: return "DHT"; case GST_JPEG_MARKER_DAC: return "DAC"; case GST_JPEG_MARKER_SOI: return "SOI"; case GST_JPEG_MARKER_EOI: return "EOI"; case GST_JPEG_MARKER_SOS: return "SOS"; case GST_JPEG_MARKER_DQT: return "DQT"; case GST_JPEG_MARKER_DNL: return "DNL"; case GST_JPEG_MARKER_DRI: return "DRI"; case GST_JPEG_MARKER_APP0: return "APP0"; case GST_JPEG_MARKER_APP1: return "APP1"; case GST_JPEG_MARKER_APP2: return "APP2"; case GST_JPEG_MARKER_APP3: return "APP3"; case GST_JPEG_MARKER_APP4: return "APP4"; case GST_JPEG_MARKER_APP5: return "APP5"; case GST_JPEG_MARKER_APP6: return "APP6"; case GST_JPEG_MARKER_APP7: return "APP7"; case GST_JPEG_MARKER_APP8: return "APP8"; case GST_JPEG_MARKER_APP9: return "APP9"; case GST_JPEG_MARKER_APP10: return "APP10"; case GST_JPEG_MARKER_APP11: return "APP11"; case GST_JPEG_MARKER_APP12: return "APP12"; case GST_JPEG_MARKER_APP13: return "APP13"; case GST_JPEG_MARKER_APP14: return "APP14"; case GST_JPEG_MARKER_APP15: return "APP15"; case GST_JPEG_MARKER_COM: return "COM"; default: if (marker > GST_JPEG_MARKER_RST_MIN && marker < GST_JPEG_MARKER_RST_MAX) return "RST"; break; } return "???"; } static gboolean parse_jpeg_segment (GstJpegSegment * segment) { switch (segment->marker) { case GST_JPEG_MARKER_SOF0: case GST_JPEG_MARKER_SOF1: case GST_JPEG_MARKER_SOF2: case GST_JPEG_MARKER_SOF3: case GST_JPEG_MARKER_SOF9: case GST_JPEG_MARKER_SOF10: case GST_JPEG_MARKER_SOF11:{ GstJpegFrameHdr hdr; int i; if (!gst_jpeg_segment_parse_frame_header (segment, &hdr)) { g_printerr ("Failed to parse frame header!\n"); return FALSE; } g_print ("\t\twidth x height = %u x %u\n", hdr.width, hdr.height); g_print ("\t\tsample precision = %u\n", hdr.sample_precision); g_print ("\t\tnum components = %u\n", hdr.num_components); for (i = 0; i < hdr.num_components; ++i) { g_print ("\t\t%d: id=%d, h=%d, v=%d, qts=%d\n", i, hdr.components[i].identifier, hdr.components[i].horizontal_factor, hdr.components[i].vertical_factor, hdr.components[i].quant_table_selector); } break; } case GST_JPEG_MARKER_DHT:{ GstJpegHuffmanTables ht; if (!gst_jpeg_segment_parse_huffman_table (segment, &ht)) { g_printerr ("Failed to parse huffman table!\n"); return FALSE; } break; } case GST_JPEG_MARKER_DQT:{ GstJpegQuantTables qt; if (!gst_jpeg_segment_parse_quantization_table (segment, &qt)) { g_printerr ("Failed to parse quantization table!\n"); return FALSE; } break; } case GST_JPEG_MARKER_SOS:{ GstJpegScanHdr hdr; int i; if (!gst_jpeg_segment_parse_scan_header (segment, &hdr)) { g_printerr ("Failed to parse scan header!\n"); return FALSE; } g_print ("\t\tnum components = %u\n", hdr.num_components); for (i = 0; i < hdr.num_components; ++i) { g_print ("\t\t %d: cs=%d, dcs=%d, acs=%d\n", i, hdr.components[i].component_selector, hdr.components[i].dc_selector, hdr.components[i].ac_selector); } } case GST_JPEG_MARKER_COM: /* gst_util_dump_mem (segment->data + segment->offset, segment->size); */ break; default: if (segment->marker >= GST_JPEG_MARKER_APP_MIN && segment->marker <= GST_JPEG_MARKER_APP_MAX) { guint n = segment->marker - GST_JPEG_MARKER_APP_MIN; if (app_segments[n] == NULL) app_segments[n] = gst_buffer_new (); gst_buffer_append_memory (app_segments[n], gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY, (guint8 *) segment->data + segment->offset, segment->size, 0, segment->size, NULL, NULL)); } break; } return TRUE; } static void parse_jpeg (const guint8 * data, gsize size) { GstJpegSegment segment; guint offset = 0; while (gst_jpeg_parse (&segment, data, size, offset)) { if (segment.offset > offset + 2) g_print (" skipped %u bytes\n", (guint) segment.offset - offset - 2); g_print ("%6d bytes at offset %-8u : %s\n", (gint) segment.size, segment.offset, get_marker_name (segment.marker)); if (segment.marker == GST_JPEG_MARKER_EOI) break; if (offset + segment.size < size && parse_jpeg_segment (&segment) && segment.size >= 0) offset = segment.offset + segment.size; else offset += 2; }; } static void process_file (const gchar * fn) { GError *err = NULL; gchar *data = NULL; gsize size = 0; guint i; g_print ("===============================================================\n"); g_print (" %s\n", fn); g_print ("===============================================================\n"); if (!g_file_get_contents (fn, &data, &size, &err)) { g_error ("%s", err->message); g_clear_error (&err); return; } parse_jpeg ((const guint8 *) data, size); for (i = 0; i < G_N_ELEMENTS (app_segments); ++i) { if (app_segments[i] != NULL) { GstMapInfo map = GST_MAP_INFO_INIT; /* Could parse/extract tags here */ gst_buffer_map (app_segments[i], &map, GST_MAP_READ); g_print ("\tAPP%-2u : %u bytes\n", i, (guint) map.size); gst_util_dump_mem ((guchar *) map.data, MIN (map.size, 16)); gst_buffer_unmap (app_segments[i], &map); gst_buffer_unref (app_segments[i]); app_segments[i] = NULL; } } g_free (data); } int main (int argc, gchar ** argv) { gchar **filenames = NULL; GOptionEntry options[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, {NULL} }; GOptionContext *ctx; GError *err = NULL; guint i, num; gst_init (&argc, &argv); if (argc == 1) { g_printerr ("Usage: %s FILE.JPG [FILE2.JPG] [FILE..JPG]\n", argv[0]); return -1; } ctx = g_option_context_new ("JPEG FILES"); g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE); g_option_context_add_group (ctx, gst_init_get_option_group ()); if (!g_option_context_parse (ctx, &argc, &argv, &err)) { g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); g_option_context_free (ctx); g_clear_error (&err); exit (1); } g_option_context_free (ctx); if (filenames == NULL || *filenames == NULL) { g_printerr ("Please provide one or more filenames."); return 1; } num = g_strv_length (filenames); for (i = 0; i < num; ++i) { process_file (filenames[i]); } g_strfreev (filenames); return 0; }