indexing description: "[ A SMJPEG decoder ]" date: "$Date$" revision: "$Revision$" class EM_SMJPEG_DECODER inherit EM_VIDEO_DECODER EM_VIDEO_DECODER_CONSTANTS export {NONE} all end EM_TIME_SINGLETON export {NONE} all end EM_SHARED_BITMAP_FACTORY export {NONE} all end EM_AUDIO_CONSTANTS export {NONE} all end EM_SHARED_ERROR_HANDLER export {NONE} all end EM_SHARED_SCENE export {NONE} all end create {EM_VIDEO_DECODER_FACTORY} make feature {NONE} -- Initialisation make is -- init do width := 1 height := 1 load_image (Void) set_visible (True) create on_stop end feature {EM_VIDEO_DECODER_FACTORY} -- Request accept_file (file: KI_BINARY_INPUT_FILE): BOOLEAN is -- accept file do if file.is_rewindable then file.rewind else file.close file.open_read end file.read_string (em_smjpeg_magic.count) Result := file.last_string.is_equal (em_smjpeg_magic) end is_valid_file (file: KI_BINARY_INPUT_FILE): BOOLEAN is -- is file a valid file do Result := true if file.is_rewindable then file.rewind else file.close file.open_read end file.read_string (8) file.read_string (4) if file.last_string.count /= 4 then Result := false elseif Result and then string_to_integer (file.last_string) /= 0 then Result := false end if Result then file.read_string (4) if file.last_string.count /= 4 then Result := false elseif string_to_integer (file.last_string) = 0 then Result := false end from file.read_string (4) until file.last_string.is_equal (em_smjpeg_header_end) or file.last_string.count /= 4 or not Result loop if file.last_string.is_equal (em_smjpeg_comment_header) or file.last_string.is_equal (em_smjpeg_sound_header) or file.last_string.is_equal (em_smjpeg_video_header) then file.read_string (4) if file.last_string.count /= 4 then Result := false else file.read_string (string_to_integer (file.last_string)) end else Result := false end file.read_string (4) end if not file.last_string.is_equal (em_smjpeg_header_end) then Result := false else from file.read_string (4) until file.last_string.is_equal (em_smjpeg_file_end) or file.last_string.count /= 4 or not Result or not (file.last_string.is_equal (em_smjpeg_video_chunk) or file.last_string.is_equal (em_smjpeg_sound_chunk)) loop file.read_string (4) file.read_string (4) if file.last_string.count /= 4 then Result := false else file.read_string (string_to_integer (file.last_string)) end file.read_string (4) end if not file.last_string.is_equal (em_smjpeg_file_end) then Result := false end end end end feature -- Status report position: INTEGER is -- position local tmp_loops_left: INTEGER do if is_playing or is_paused then if is_playing then Result := pos + time.ticks - last_pos_update else Result := pos end if Result > video_length then from tmp_loops_left := loops_left until Result <= video_length loop if tmp_loops_left = 0 then Result := video_length else Result := Result - video_length end if tmp_loops_left > 0 then tmp_loops_left := tmp_loops_left - 1 end end end else Result := pos if Result > video_length then Result := video_length end end end video_length: INTEGER type: STRING is "SMJPEG" file_name: STRING feature {EM_VIDEO_DECODER_FACTORY} -- Load load_from_file (file: KI_BINARY_INPUT_FILE) is -- load smjpeg video from file do header_length := 20 video_file := file file_name := file.name if file.is_rewindable then file.rewind else file.close file.open_read end file.read_string (12) file.read_string (4) video_length := string_to_integer (file.last_string) from file.read_string (4) until file.last_string.is_equal (em_smjpeg_header_end) loop if file.last_string.is_equal (em_smjpeg_comment_header) then read_comment elseif file.last_string.is_equal (em_smjpeg_sound_header) then read_sound_header elseif file.last_string.is_equal (em_smjpeg_video_header) then read_video_header end file.read_string (4) header_length := header_length + 4 end -- end header if has_audio then audio_subsystem.mixer.on_before_close.subscribe (agent unload) create audio_stream.make (audio_buffer) end is_loaded := true clear_buffer load_image (Void) running_scene.event_loop.update_event.subscribe (agent outside) end feature {NONE} -- Unload unload is -- shoud be called after use do is_loaded := false if not audio_stream.is_playing then audio_stream.start end video_file.close end feature -- Play play (loops: INTEGER) is -- play video do if loops /= 0 then pos := 0 is_playing := true if loops > 0 then loops_left := loops - 1 else loops_left := loops end last_pos_update := time.ticks if has_audio and not audio_stream.is_playing then audio_stream.start end end end feature -- Pause pause is -- pause video do pos := pos + time.ticks - last_pos_update is_playing := false is_paused := true if has_audio then audio_stream.stop end end resume is -- resume playback do is_paused := false is_playing := true last_pos_update := time.ticks if has_audio then audio_stream.start end end feature -- Stop stop is -- stop video do is_playing := false is_paused := false if has_audio and audio_stream.is_playing then audio_stream.stop end pos := 0 clear_buffer on_stop.publish ([]) end feature -- Status setting go_to_position (abs: INTEGER) is -- go to absolute position do pos := abs last_pos_update := time.ticks clear_buffer end rewind is -- go to beginning do pos := 0 last_pos_update := time.ticks clear_buffer end seek (interval: INTEGER) is -- seek video do pos := pos + interval if pos < 0 then pos := 0 end clear_buffer end feature -- Event on_stop: EM_EVENT_CHANNEL [TUPLE] feature -- Drawing draw (a_surface: EM_SURFACE) is -- draw video local pic: EM_BITMAP do if is_visible then pic := picture pic.set_x_y (x, y) pic.draw (a_surface) end end feature {NONE} -- Audio playback audio_stream: EM_AUDIO_MOVIE_STREAM audio_buffer: EM_BINARY_RING_BUFFER feature {NONE} -- Implementation outside is -- outside reading function local foo: BOOLEAN do if is_playing then update_pos end foo := buffered_read end read_string_from_file (n: INTEGER) is -- read n characters from filestream do if video_file.is_open_read and n > 0 and not video_file.end_of_input then video_file.read_string (n) end end feature {NONE} -- Buffer reading clear_buffer is -- clear all buffers local restart: BOOLEAN do if has_audio and audio_stream.is_playing then audio_stream.stop restart := true end if video_file.is_rewindable then video_file.rewind else video_file.close video_file.open_read end video_file.read_string (header_length) buffered_audio := Void buffered_image := Void last_timestamp := 0 if has_audio then audio_buffer.clear end if restart then audio_stream.start end bevore_data := false end bevore_data: BOOLEAN buffered_read: BOOLEAN is -- read stream and fill buffer -- returns true if it has read something local buffer_full: BOOLEAN do if position < video_length then if last_timestamp <= position - 100 then from if bevore_data then read_string_from_file (4) read_string_from_file (string_to_integer (video_file.last_string)) end read_string_from_file (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else read_string_from_file (4) last_timestamp := string_to_integer (video_file.last_string) end bevore_data := true until last_timestamp > position loop read_string_from_file (4) read_string_from_file (string_to_integer (video_file.last_string)) read_string_from_file (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else read_string_from_file (4) last_timestamp := string_to_integer (video_file.last_string) end end end if not bevore_data then read_string_from_file (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else read_string_from_file (4) last_timestamp := string_to_integer (video_file.last_string) end end from until not last_read_is_sound or buffer_full or last_timestamp > video_length loop buffer_full := fill_audio_buffer if last_read_is_sound and not buffer_full then Result := true end end if not last_read_is_sound and buffered_image = Void and not video_file.end_of_input then fill_image_buffer Result := true end if buffered_image_timestamp <= position and (is_playing or is_paused) then load_image (buffered_image) buffered_image := Void Result := true end end end fill_image_buffer is -- fill image buffer local len: INTEGER do if last_timestamp < video_length then buffered_image_timestamp := last_timestamp read_string_from_file (4) len := string_to_integer (video_file.last_string) if len > 0 then create buffered_image.make (len) end if video_file.read_to_buffer (buffered_image, 1, len) < len then io.put_string ("Error: this shoud never happen%N") io.output.flush end read_string_from_file (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else read_string_from_file (4) last_timestamp := string_to_integer (video_file.last_string) end bevore_data := true else buffered_image_timestamp := video_length + 1 buffered_image := Void end end fill_audio_buffer: BOOLEAN is -- fill audio_buffer local len: INTEGER do if buffered_audio /= Void then len := buffered_audio.count else read_string_from_file (4) len := string_to_integer (video_file.last_string) end if not has_audio or len <= 0 then Result := false elseif audio_encoding = em_smjpeg_audio_adpcm then Result := audio_buffer.free < len * 4 - 16 * audio_channels else Result := audio_buffer.free < len end if not Result then if audio_encoding = em_smjpeg_audio_adpcm or not has_audio then if buffered_audio = Void or not has_audio then create buffered_audio.make (len) if video_file.read_to_buffer (buffered_audio, 1, len) < len then io.put_string ("Error: this shoud never happen%N") io.output.flush end video_file.read_string (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else video_file.read_string (4) last_timestamp := string_to_integer (video_file.last_string) end end if has_audio then audio_buffer.write_from_character_buffer (adpcm_decoded (buffered_audio)) end buffered_audio := Void else if buffered_audio /= Void then audio_buffer.write_from_character_buffer (buffered_audio) buffered_audio := Void else if audio_buffer.write_from_stream (video_file, len) < len then io.put_string ("Error: this shoud never happen%N") io.output.flush else video_file.read_string (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else video_file.read_string (4) last_timestamp := string_to_integer (video_file.last_string) end end end end elseif buffered_audio = Void and len > 0 then create buffered_audio.make (len) if video_file.read_to_buffer (buffered_audio, 1, len) < len then io.put_string ("Error: this shoud never happen%N") io.output.flush else read_string_from_file (4) last_read_is_sound := video_file.last_string.is_equal (em_smjpeg_sound_chunk) if video_file.last_string.is_equal (em_smjpeg_file_end) then last_timestamp := video_length + 1 else read_string_from_file (4) last_timestamp := string_to_integer (video_file.last_string) end end end end last_read_is_sound: BOOLEAN last_timestamp: INTEGER buffered_audio: KL_CHARACTER_BUFFER buffered_image: KL_CHARACTER_BUFFER buffered_image_timestamp: INTEGER feature {NONE} -- Video playback load_image (ar: like buffered_image) is -- load current image do if ar /= Void then bitmap_factory.create_bitmap_from_c_array (ar.as_special.base_address + 1, ar.count) if bitmap_factory.last_bitmap = Void then bitmap_factory.create_empty_bitmap (width, height) end picture := bitmap_factory.last_bitmap else bitmap_factory.create_empty_bitmap (width, height) picture := bitmap_factory.last_bitmap end ensure picture_loaded: picture /= Void end feature {NONE} -- Implementation adpcm_decoded (ar: like buffered_audio): like buffered_audio is -- return decoded audio data require ar_exist: ar /= Void and then ar.count > 0 local i, j: INTEGER -- position in ar and Result converted: INTEGER -- # already converted pred, pdiff: INTEGER -- predicated value and diff index, step, inputbuffer, delta, sign: INTEGER bufferstep: BOOLEAN state: ARRAY [TUPLE [INTEGER, INTEGER]] -- prev and index do create Result.make (ar.count * 4 - 16 * audio_channels) i := 4 * audio_channels + 1 j := 1 bufferstep := false create state.make (0, audio_channels - 1) from converted := 0 until converted >= audio_channels loop pred := ((ar.item (converted * 4 + 1).code |<< 8) | ar.item (converted * 4 + 2).code).to_integer_16.to_integer if pred > 32767 then pred := 32767 elseif pred < -32768 then pred := -32768 end state.force ([pred.to_integer_16.to_integer, ar.item (converted * 4 + 3).code.to_integer_8.to_integer], converted) if state.item (converted).integer_item (2) > 88 then state.item (converted).put_integer (88, 2) elseif state.item (converted).integer_item (2) < 0 then state.item (converted).put_integer (0, 2) end converted := converted + 1 end from converted := 0 until ar.count * 2 - 8 * audio_channels <= converted loop -- init pred := state.item (converted \\ audio_channels).integer_item (1) index := state.item (converted \\ audio_channels).integer_item (2) step := em_adpcm_stepsize_table.item (index) -- get delta if bufferstep then delta := inputbuffer & 0xF else inputbuffer := ar.item (i).code i := i + 1 delta := (inputbuffer |>> 4) & 0xF end bufferstep := not bufferstep -- new index index := index + em_adpcm_index_table.item (delta) if index < 0 then index := 0 elseif index > 88 then index := 88 end -- seperate sign and magnitude sign := delta & 8 delta := delta & 7 -- compute difference and new pred pdiff := step |>> 3 if delta & 4 /= 0 then pdiff := pdiff + step end if delta & 2 /= 0 then pdiff := pdiff + (step |>> 1) end if delta & 1 /= 0 then pdiff := pdiff + (step |>> 2) end if sign /= 0 then pred := pred - pdiff else pred := pred + pdiff end -- rest in range if pred > 32767 then pred := 32767 elseif pred < -32768 then pred := -32768 end -- output Result.put (pred.to_integer_8.to_character, j) j := j + 1 Result.put ((pred |>> 8).to_integer_8.to_character, j) j := j + 1 -- state saving state.force ([pred, index], converted \\ audio_channels) converted := converted + 1 end end string_to_integer (str: STRING): INTEGER is -- convert a string to an integer -- it should be unsigned but there wouldn't be such big numbers require str_exist: str /= Void and then not str.is_empty str_has_length_4: str.count = 4 do Result := str.item (1).code |<< 24 Result := Result | (str.item (2).code |<< 16) Result := Result | (str.item (3).code |<< 8) Result := Result | str.item (4).code end feature {NONE} -- Reading functions read_comment is -- read comment header -- ignored local len: INTEGER do video_file.read_string (4) len := string_to_integer (video_file.last_string) video_file.read_string (len) header_length := header_length + 4 + len end read_sound_header is -- read sound header local len, format: INTEGER do video_file.read_string (4) len := string_to_integer (video_file.last_string) header_length := header_length + 4 + len if len >= em_smjpeg_audio_header_length then has_audio := true video_file.read_string (2) audio_rate := video_file.last_string.item (1).code |<< 8 audio_rate := audio_rate | video_file.last_string.item (2).code if audio_rate <= 0 then has_audio := false end video_file.read_character audio_bits_per_sample := video_file.last_character.code if audio_bits_per_sample <= 0 then has_audio := false end video_file.read_character audio_channels := video_file.last_character.code if audio_channels <= 0 then has_audio := false end video_file.read_string (4) audio_encoding := string_to_integer (video_file.last_string) if audio_encoding /= em_smjpeg_audio_adpcm and audio_encoding /= em_smjpeg_audio_none then has_audio := false end if len - em_smjpeg_audio_header_length > 0 then video_file.read_string (len - em_smjpeg_audio_header_length) end else video_file.read_string (len) end if has_audio then if audio_subsystem.is_enabled then audio_subsystem.disable end audio_subsystem.enable format := em_audio_format_s16sys if audio_bits_per_sample = 8 then format :=em_audio_format_u8 elseif audio_bits_per_sample = 16 then format := em_audio_format_s16lsb end audio_subsystem.mixer.open (audio_rate, format, audio_channels, em_default_chunk_size) if audio_subsystem.mixer.is_open then create audio_buffer.make (em_default_chunk_size * 10) else has_audio := false end end end read_video_header is -- read video header local len: INTEGER do video_file.read_string (4) len := string_to_integer (video_file.last_string) header_length := header_length + 4 + len if len >= em_smjpeg_video_header_length then has_video := true video_file.read_string (4) video_frames := string_to_integer (video_file.last_string) if video_frames < 1 then has_video := false end video_file.read_string (2) width := video_file.last_string.item (1).code |<< 8 width := width | video_file.last_string.item (2).code if width < 1 then has_video := false end video_file.read_string (2) height := video_file.last_string.item (1).code |<< 8 height := height | video_file.last_string.item (2).code if height < 1 or not has_video then height := 1 width := 1 has_video := false end video_file.read_string (4) video_encoding := string_to_integer (video_file.last_string) if video_encoding /= em_smjpeg_video_jpeg then has_video := false end if len - em_smjpeg_video_header_length > 0 then video_file.read_string (len - em_smjpeg_video_header_length) end else video_file.read_string (len) end end feature {NONE} -- Intern status loops_left: INTEGER picture: EM_BITMAP feature {NONE} -- Intern position last_pos_update: INTEGER -- last time when pos was updated pos: INTEGER -- intern position update_pos is -- update pos require playing: is_playing local tmp: INTEGER do tmp := time.ticks pos := pos + tmp - last_pos_update last_pos_update := tmp if pos >= video_length then from until pos < video_length or not is_playing loop if loops_left /= 0 then pos := pos - video_length if loops_left > 0 then loops_left := loops_left - 1 end else is_playing := false if has_audio then audio_stream.stop end pos := 0 on_stop.publish ([]) end end clear_buffer end end feature {NONE} -- File infos video_file: KI_BINARY_INPUT_FILE header_length: INTEGER video_frames: INTEGER video_encoding: INTEGER audio_rate: INTEGER audio_bits_per_sample: INTEGER audio_channels: INTEGER audio_encoding: INTEGER invariant picture_loaded: picture /= Void file_loaded: is_loaded implies video_file.is_open_read video_settings_set: has_video implies video_frames > 0 and video_encoding = em_smjpeg_video_jpeg audio_settings_set: has_audio implies audio_rate > 0 and audio_bits_per_sample > 0 and audio_channels > 0 and audio_subsystem.is_enabled valid_audio_encoding: has_audio implies audio_encoding = em_smjpeg_audio_adpcm or audio_encoding = em_smjpeg_audio_none mixer_opend_correct: has_audio implies audio_subsystem.mixer.frequency = audio_rate and audio_subsystem.mixer.output_channel = audio_channels end