Skip to content

Commit 85e7c41

Browse files
authored
Merge pull request #3 from BinaryNoggin/jpeg-to-yuv
[API BREAKING] Add feature to extract pixel data from jpegs
2 parents a6bb6e3 + cd02621 commit 85e7c41

File tree

6 files changed

+200
-43
lines changed

6 files changed

+200
-43
lines changed

c_src/turbojpeg_native/turbojpeg_native.c

Lines changed: 172 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,55 +4,199 @@
44
* Supported pixel formats: :I420 | :I422 | :I444
55
* Unsupported pixel formats: :RGB | :BGRA | :RGBA | :NV12 | :NV21 | :YV12 | :AYUV
66
*/
7-
UNIFEX_TERM create(UnifexEnv *env, int width, int height, int jpegQuality, char *format) {
8-
UNIFEX_TERM res;
9-
State *state = unifex_alloc_state(env);
10-
state->width = width; state->height = height;
11-
state->flags = 0; state->quality = jpegQuality;
7+
int format_to_tjsamp(char* format) {
128
if(strcmp(format, "I420") == 0) {
13-
state->format = TJSAMP_420;
9+
return TJSAMP_420;
1410
} else if(strcmp(format, "I422") == 0) {
15-
state->format = TJSAMP_422;
11+
return TJSAMP_422;
1612
} else if(strcmp(format, "I444") == 0) {
17-
state->format = TJSAMP_444;
13+
return TJSAMP_444;
1814
} else {
19-
res = create_result_error(env, "unsupported_format");
20-
unifex_release_state(env, state);
21-
return(res);
15+
return -1;
2216
}
23-
res = create_result_ok(env, state);
24-
unifex_release_state(env, state);
25-
return(res);
17+
}
18+
19+
/**
20+
* Supported pixel formats: :I420 | :I422 | :I444
21+
* Unsupported pixel formats: :RGB | :BGRA | :RGBA | :NV12 | :NV21 | :YV12 | :AYUV
22+
*/
23+
const char* tjsamp_to_format(enum TJSAMP tjsamp) {
24+
switch(tjsamp) {
25+
case(TJSAMP_420):
26+
return("I420");
27+
case(TJSAMP_422):
28+
return("I422");
29+
case(TJSAMP_444):
30+
return("I444");
31+
default:
32+
return("unknown_format");
33+
}
34+
}
35+
36+
/**
37+
* Returns %{height: int, width: int, format: atom}
38+
*/
39+
UNIFEX_TERM get_jpeg_header(UnifexEnv* env, UnifexPayload *payload) {
40+
tjhandle tjh;
41+
enum TJSAMP tjsamp;
42+
enum TJCS cspace;
43+
int res, width, height;
44+
ERL_NIF_TERM map_out, ret;
45+
46+
tjh = tjInitDecompress();
47+
if(!tjh)
48+
return get_jpeg_header_result_error(env, tjGetErrorStr());
49+
50+
res = tjDecompressHeader3(
51+
tjh,
52+
payload->data,
53+
payload->size,
54+
&width, &height,
55+
(int*)&tjsamp, (int*)&cspace
56+
);
57+
if(res < 0) {
58+
ret = get_jpeg_header_result_error(env, tjGetErrorStr2(tjh));
59+
goto cleanup;
60+
}
61+
62+
// unifex does not support maps yes.
63+
// See https://github.com/membraneframework/unifex/issues/32
64+
if(!enif_make_map_from_arrays(
65+
env,
66+
(ERL_NIF_TERM []) {
67+
enif_make_atom(env, "width"),
68+
enif_make_atom(env, "height"),
69+
enif_make_atom(env, "format"),
70+
71+
},
72+
(ERL_NIF_TERM []) {
73+
enif_make_int(env, width),
74+
enif_make_int(env, height),
75+
enif_make_atom(env, tjsamp_to_format(tjsamp))
76+
},
77+
3, &map_out
78+
)) {
79+
ret = get_jpeg_header_result_error(env, "make_map");
80+
goto cleanup;
81+
} else {
82+
// Generated code does not support maps currently.
83+
ret = enif_make_tuple_from_array(env, (ERL_NIF_TERM []) {enif_make_atom(env, "ok"), map_out}, 2);
84+
goto cleanup;
85+
}
86+
cleanup:
87+
if(tjh) tjDestroy(tjh);
88+
return ret;
2689
}
2790

2891
/**
2992
* Convert a binary h264 payload into a jpeg encoded payload
3093
*/
31-
UNIFEX_TERM to_jpeg(UnifexEnv* env, UnifexPayload *payload, State *state) {
32-
state->tjh = tjInitCompress();
33-
94+
UNIFEX_TERM yuv_to_jpeg(UnifexEnv* env, UnifexPayload *payload, int width, int height, int quality, char* format) {
95+
tjhandle tjh = NULL;
96+
enum TJSAMP tjsamp;
3497
unsigned char *jpegBuf = NULL;
3598
unsigned long jpegSize;
99+
int res;
100+
UnifexPayload *jpegFrame;
101+
UNIFEX_TERM ret;
36102

37-
int res = tjCompressFromYUV(
38-
state->tjh,
103+
res = format_to_tjsamp(format);
104+
if(res < 0) {
105+
return(yuv_to_jpeg_result_error(env, "unsupported_format"));
106+
} else {
107+
tjsamp = (enum TJSAMP)res;
108+
}
109+
110+
tjh = tjInitCompress();
111+
if(!tjh)
112+
return yuv_to_jpeg_result_error(env, tjGetErrorStr());
113+
114+
res = tjCompressFromYUV(
115+
tjh,
39116
payload->data,
40-
state->width, 4, state->height, state->format,
117+
width, 4, height, tjsamp,
41118
&jpegBuf, &jpegSize,
42-
state->quality, state->flags
119+
quality, 0
43120
);
44121

45-
if(res) {
46-
return to_jpeg_result_error(env, tjGetErrorStr());
122+
if(res < 0) {
123+
ret = yuv_to_jpeg_result_error(env, tjGetErrorStr2(tjh));
124+
goto cleanup;
47125
} else {
48-
UnifexPayload *jpegFrame = unifex_payload_alloc(env, UNIFEX_PAYLOAD_SHM, jpegSize);
49-
memcpy(jpegFrame->data, jpegBuf, jpegSize);
50-
tjFree(jpegBuf);
51-
return to_jpeg_result_ok(env, jpegFrame);
126+
jpegFrame = unifex_payload_alloc(env, UNIFEX_PAYLOAD_SHM, jpegSize);
127+
if(!jpegFrame) {
128+
ret = yuv_to_jpeg_result_error(env, "payload_alloc");
129+
} else {
130+
memcpy(jpegFrame->data, jpegBuf, jpegSize);
131+
ret = yuv_to_jpeg_result_ok(env, jpegFrame);
132+
}
133+
goto cleanup;
52134
}
135+
136+
cleanup:
137+
if(jpegBuf) tjFree(jpegBuf);
138+
if(tjh) tjDestroy(tjh);
139+
return ret;
140+
}
141+
142+
/**
143+
* Convert a binary jpeg payload into a yuv encoded payload
144+
*/
145+
UNIFEX_TERM jpeg_to_yuv(UnifexEnv* env, UnifexPayload *payload) {
146+
tjhandle tjh;
147+
enum TJSAMP tjsamp;
148+
enum TJCS cspace;
149+
unsigned long yuvBufSize;
150+
UnifexPayload *yuvFrame;
151+
int res, width, height;
152+
UNIFEX_TERM ret;
153+
154+
tjh = tjInitDecompress();
155+
if(!tjh)
156+
return jpeg_to_yuv_result_error(env, tjGetErrorStr());
157+
158+
res = tjDecompressHeader3(
159+
tjh,
160+
payload->data,
161+
payload->size,
162+
&width, &height,
163+
(int*)&tjsamp, (int*)&cspace
164+
);
165+
if(res < 0) {
166+
ret = jpeg_to_yuv_result_error(env, tjGetErrorStr2(tjh));
167+
goto cleanup;
168+
}
169+
170+
yuvBufSize = tjBufSizeYUV2(width, 4, height, tjsamp);
171+
yuvFrame = unifex_payload_alloc(env, UNIFEX_PAYLOAD_SHM, yuvBufSize);
172+
if(!yuvFrame) {
173+
ret = jpeg_to_yuv_result_error(env, "could not allocate frame");
174+
goto cleanup;
175+
}
176+
177+
res = tjDecompressToYUV2(
178+
tjh,
179+
payload->data,
180+
payload->size,
181+
yuvFrame->data,
182+
width, 4, height,
183+
0
184+
);
185+
186+
if(res < 0) {
187+
ret = jpeg_to_yuv_result_error(env, tjGetErrorStr2(tjh));
188+
goto cleanup;
189+
} else {
190+
ret = jpeg_to_yuv_result_ok(env, yuvFrame);
191+
goto cleanup;
192+
}
193+
194+
cleanup:
195+
if(tjh) tjDestroy(tjh);
196+
return ret;
53197
}
54198

55199
void handle_destroy_state(UnifexEnv* env, State* state) {
56200
UNIFEX_UNUSED(env);
57-
if(state->tjh) { tjDestroy(state->tjh); }
201+
UNIFEX_UNUSED(state);
58202
}

c_src/turbojpeg_native/turbojpeg_native.h

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,10 @@
44
#include <turbojpeg.h>
55

66
/** NIF State */
7-
typedef struct _h264_parser_state {
8-
/** Handle to turbojpeg */
9-
tjhandle tjh;
10-
enum TJSAMP format;
11-
int flags;
12-
int width; int height; int quality;
7+
typedef struct _turbojpeg_native_state {
138
} UnifexNifState;
149

15-
/** NIF State */
10+
// /** NIF State */
1611
typedef UnifexNifState State;
1712

1813
#include "_generated/turbojpeg_native.h"
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
module Turbojpeg.Native
22

3-
spec create(width::int, height::int, quality::int, format::atom) :: {:ok :: label, state} | {:error :: label, reason :: atom}
4-
spec to_jpeg(payload, state) :: {:ok :: label, payload} | {:error :: label, reason :: atom}
3+
spec yuv_to_jpeg(payload, width::int, height::int, quality::int, format::atom) :: {:ok :: label, payload} | {:error :: label, reason :: atom}
4+
spec jpeg_to_yuv(payload) :: {:ok :: label, payload} | {:error :: label, reason :: atom}
5+
spec get_jpeg_header(payload) :: {:ok :: label, data::int} | {:error::label, reason::atom}

fixture/ff0000_i444.jpg

744 Bytes
Loading

lib/turbojpeg/sink.ex

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ defmodule Turbojpeg.Sink do
1515
def handle_init(options) do
1616
state = %{
1717
quality: options.quality,
18+
height: nil,
19+
width: nil,
20+
format: nil,
1821
filename: options.filename,
19-
jpeg_ref: nil,
2022
timer_started?: false
2123
}
2224

@@ -37,16 +39,16 @@ defmodule Turbojpeg.Sink do
3739
%{input: input} = ctx.pads
3840

3941
if !input.caps || caps == input.caps do
40-
{:ok, ref} = Native.create(caps.width, caps.height, state.quality, caps.format)
41-
{:ok, %{state | jpeg_ref: ref}}
42+
{:ok, %{state | width: caps.width, height: caps.height, format: caps.format}}
4243
else
4344
raise "Caps have changed while playing. This is not supported."
4445
end
4546
end
4647

4748
@impl true
4849
def handle_write(:input, %Buffer{payload: payload}, _ctx, state) do
49-
with {:ok, data} <- Native.to_jpeg(payload, state.jpeg_ref),
50+
with {:ok, data} <-
51+
Native.yuv_to_jpeg(payload, state.width, state.height, state.quality, state.format),
5052
:ok <- File.write(state.filename, Shmex.to_binary(data)) do
5153
{:ok, state}
5254
else

test/turbojpeg_test.exs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,27 @@ defmodule TurbojpegTest do
22
use ExUnit.Case
33
@jpeg_header <<255, 216, 255>>
44
@i420_fixture "fixture/i420.yuv"
5+
@ff0000_fixture "fixture/ff0000_i444.jpg"
56

67
test "Converts an i420 frame into a jpeg" do
7-
{:ok, native} = Turbojpeg.Native.create(1920, 1080, 75, :I420)
88
frame = File.read!(@i420_fixture)
99
shmex = Shmex.new(frame)
10-
{:ok, jpeg} = Turbojpeg.Native.to_jpeg(shmex, native)
10+
{:ok, jpeg} = Turbojpeg.Native.yuv_to_jpeg(shmex, 1920, 1080, 100, :I420)
1111
assert match?(@jpeg_header <> _, Shmex.to_binary(jpeg))
1212
end
13+
14+
test "extracts i444 frame from jpeg" do
15+
jpeg = Shmex.new(File.read!(@ff0000_fixture))
16+
{:ok, yuv} = Turbojpeg.Native.jpeg_to_yuv(jpeg)
17+
{:ok, new_jpeg} = Turbojpeg.Native.yuv_to_jpeg(yuv, 64, 64, 100, :I444)
18+
assert Shmex.to_binary(jpeg) == Shmex.to_binary(new_jpeg)
19+
end
20+
21+
test "get jpeg header" do
22+
jpeg = Shmex.new(File.read!(@ff0000_fixture))
23+
{:ok, result} = Turbojpeg.Native.get_jpeg_header(jpeg)
24+
assert result.width == 64
25+
assert result.height == 64
26+
assert result.format == :I444
27+
end
1328
end

0 commit comments

Comments
 (0)