Transcoder - hereforu/tizen-multimedia-apps GitHub Wiki
##Multimedia Native App ๋ง๋ค๊ธฐ - 1. Transcoder**
- Transcoder Application ๊ฐ์
- ํ๋ฉด ๊ตฌ์ฑ
- Media Demuxer, Media Codec, Media Muxer ํ์ฉ
- Transcoding
Transcoder Application์ ๋จ๋ง์ ์ ์ฅ๋์ด ์๋ ๋์์ ์ค ํ๋๋ฅผ ์ ํํ๋ฉด
- video codec
- video resolution
- audio codec
์ ๋ณ๊ฒฝํ์ฌ ์๋ก์ด ๋์์์ ์์ฑํ์ฌ ํ์ผ๋ก ์ ์ฅํ๋ Application์ด๋ค.
๊ฐ๋ฐ์๋ Transcoder Application์ ํตํด Tizen 3.0์ ์๋กญ๊ฒ ์ถ๊ฐ๋ Media Demuxer์ Media Muxer ์ฌ์ฉ๋ฒ๋ฟ๋ง ์๋๋ผ, Tizen 2.4 feature ์ธ Media Codec, Media Transformer์ ํ์ฉ ์๋ ํ์ธํ ์ ์๋ค.
- VideoListView: Transcoding ๋์ ํ์ผ ์ ํ ํ๋ฉด
- InfoView: ์ ํํ Video ํ์ผ ์์ธ ์ ๋ณด ์ ๊ณต ํ๋ฉด
- OptionView:Transcoding Option ์ ํ ํ๋ฉด
- Demuxer ๊ตฌํ
class Demuxer { public: Demuxer(); ~Demuxer(); void Create(const char* srcfilename); void Destroy(); void Start(); bool ReadSeample(int track_index, media_packet_h* packet); bool IsEoS(int track_index); void Stop(); unsigned int GetNumTracks(); media_format_h GetMediaFormat(int track_index); bool GetVideoDecInfo(CodecInfo& vdec); bool GetAudioDecInfo(CodecInfo& adec); int GetVideoTrackIndex(); int GetAudioTrackIndex(); private: ... };
>* `Demuxer::Create`์์๋ `mediademuxer_create`๋ฅผ ํตํด demuxer๋ฅผ ์์ฑํ๊ณ , EoS(End of Stream)์ Error Event๋ฅผ ๋ฐ๊ธฐ ์ํ Callback ํจ์๋ฅผ `mediademuxer_set_eos_cb`์ `mediademuxer_set_error_cb`๋ก ๊ฐ๊ฐ ๋ฑ๋กํ๋ค. ์ ๊ณผ์ ์ด ์ฑ๊ณตํ๋ฉด demuxingํ file์ `mediademuxer_set_data_source`๋ฅผ ํตํด ์ค์ ํ๊ณ `mediademuxer_prepare` ํจ์๋ฅผ ํตํด demuxer ์ํ๋ฅผ MEDIADEMUXER_STATE_IDLE์์ MEDIADEMUXER_STATE_READY๋ก ๋ณ๊ฒฝํ๋ค.
> ```{.c++}
void Demuxer::Create(const char* srcfilename)
{
int ret = MEDIADEMUXER_ERROR_NONE;
if((ret = mediademuxer_create(&m_demuxer)) != MEDIADEMUXER_ERROR_NONE)
throw_error_and_destroy_demuxer(m_demuxer, "fail to create demuxer: ", ret);
if((ret = mediademuxer_set_eos_cb(m_demuxer, demuxer_eos_cb, (void*)this)) != MEDIADEMUXER_ERROR_NONE)
throw_error_and_destroy_demuxer(m_demuxer, "fail to mediademuxer_set_eos_cb: ", ret);
if((ret = mediademuxer_set_error_cb(m_demuxer, demuxer_error_cb, (void*)this)) != MEDIADEMUXER_ERROR_NONE)
throw_error_and_destroy_demuxer(m_demuxer, "fail to mediademuxer_set_error_cb: ", ret);
if((ret = mediademuxer_set_data_source(m_demuxer, srcfilename)) != MEDIADEMUXER_ERROR_NONE)
throw_error_and_destroy_demuxer(m_demuxer, "fail to mediademuxer_set_data_source: ", ret);
if((ret = mediademuxer_prepare(m_demuxer)) != MEDIADEMUXER_ERROR_NONE)
throw_error_and_destroy_demuxer(m_demuxer, "fail to mediademuxer_prepare: ", ret);
}
์ฐธ๊ณ ๋ก
throw_error_and_destroy_demuxer
๋ ํจ์ ์คํ ๊ฒฐ๊ณผ๊ฐ MEDIADEMUXER_ERROR_NONE ์ด ์๋๋ฉด runtime error๋ฅผ throw ํ๊ณ demuxer ๊ฐ์ฒด๋ฅผ destroyํ๋ macro์ด๋ค.
#define throw_error_and_destroy_demuxer(demuxer, msg, error_code)
{
if(demuxer)
{
mediademuxer_destroy(demuxer);
demuxer = NULL;
}
throw std::runtime_error(std::string(msg)+AppTool::ToString(error_code));
}\
>* demuxer๊ฐ _MEDIADEMUXER_STATE_READY_ ์ํ๊ฐ ๋๋ฉด, ์ค์ ํ media file์ ๋ค์ด์๋ track๋ค์ ๋ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค. `mediademuxer_get_track_count`๋ฅผ ํตํด์ track ๊ฐ์๋ฅผ ์ป์ ์ ์๊ณ , `mediademuxer_get_track_info`๋ฅผ ํตํด์ ๊ฐ track์ media_format_h๋ฅผ ์ป์ ์ ์๋ค. ํ๋ํ medai_format_h๋ ๋ฐ๋์ `media_format_unref`๋ฅผ ํตํด์ reference count๋ฅผ ์ค์ฌ์ค์ผ ํ๋ค.
> ```{.c++}
void Demuxer::ExtractTrackinfo()
{
int trackcount = 0;
iferror_throw(mediademuxer_get_track_count(m_demuxer, &trackcount), "fail to mediademuxer_get_track_count: ");
for(int i= 0; i < trackcount; ++i)
{
TrackForDemuxer track;
iferror_throw(mediademuxer_get_track_info(m_demuxer, i, &track.info.fmt), "fail to mediademuxer_get_track_info: ");
media_format_type_e formattype;
int ret = MEDIA_FORMAT_ERROR_NONE;
if((ret = media_format_get_type(track.info.fmt, &formattype))== MEDIA_FORMAT_ERROR_NONE)
{
switch(formattype)
{
case MEDIA_FORMAT_VIDEO:
m_videotrackindex = i;
m_tracks.push_back(track);
break;
case MEDIA_FORMAT_AUDIO:
m_audiotrackindex = i;
m_tracks.push_back(track);
break;
default:
dlog_print(DLOG_ERROR, "Demuxer", "unsupportive type (%d)", (int)formattype);
break;
}
}
else
{
dlog_print(DLOG_ERROR, "Demuxer", "fail to media_format_get_type (%d)", ret);
}
}
}
- Track์ ๋ํ ์ ๋ณด๋ฅผ ์ป์๋ค๋ฉด demuxing์ ํตํด ์ป๊ณ ์ ํ๋ sample์ track์ ์ ํํ๊ณ
mediademuxer_start
๋ฅผ ํตํด MEDIADEMUXER_STATE_RUNNING ์ํ๋ก ๋ง๋ค์ด์ค๋ค.
void Demuxer::Start() { //in this sample code, we support only one video track and one audio track! if(m_videotrackindex != -1) iferror_throw(mediademuxer_select_track(m_demuxer, m_videotrackindex), "fail to mediademuxer_select_track for video: "); if(m_audiotrackindex != -1) iferror_throw(mediademuxer_select_track(m_demuxer, m_audiotrackindex), "fail to mediademuxer_select_track for audio: "); iferror_throw(mediademuxer_start(m_demuxer), "fail to mediademuxer_start: "); }
>* _MEDIADEMUXER_STATE_RUNNING_ ์ํ์์๋ ์ ํํ track์ ๊ตฌ์ฑํ๋ packet๋ค์ `mediademuxer_read_sample`๋ฅผ ํตํด ์์ฐจ์ ์ผ๋ก ์ฝ์ ์ ์๋ค. ์ด๋ ์ฃผ์ํ ์ ์ ๋ง์ง๋ง packet์ EOS flag๊ฐ ์ค์ ๋์ด ์์ง ์๋ค๋ ๊ฒ์ด๋ค. ์ฐธ๊ณ ๋ก EOS callback์ ๋ง์ง๋ง packet์ ์ฝ๊ณ ๋๋ฉด ๋ฐ์ํ๋ค. ๋ง์ง๋ง packet์ EOS flag๋ฅผ ์ค์ ํ๊ณ ์ ํ๋ค๋ฉด `media_packet_set_flags`๋ฅผ ํตํด์ ์ค์ ๊ฐ๋ฅํ๋ค.
> ```{.c++}
int Demuxer::read_sample(int track_index, media_packet_h* packet)
{
*packet = NULL;
return mediademuxer_read_sample(m_demuxer, track_index, packet);
}
- demuxing์ ์ค์งํ๊ณ ์ ํ๋ฉด
mediademuxer_stop
์ ํธ์ถํ์ฌ ๋ค์ MEDIADEMUXER_STATE_READY ์ํ๋ก ๋ง๋ค์ด ์ฃผ๋ฉด ๋๋ค. ์ ํํ๋ track ์mediademuxer_unselect_track
๋ฅผ ํตํด์ ๋ค์ ์ ํํด์ ํ๋ค. unselect๋ฅผ ํ์ง ์๊ฒ ๋๋ฉดmediamuxer_unprepare
๋ฅผ ์คํจํ๊ฒ ๋๋ค.
void Demuxer::Stop() { iferror_throw(mediademuxer_stop(m_demuxer), "fail to mediademuxer_stop: "); if(m_videotrackindex != -1) iferror_throw(mediademuxer_unselect_track(m_demuxer, m_videotrackindex), "fail to mediademuxer_unselect_track for video: "); if(m_audiotrackindex != -1) iferror_throw(mediademuxer_unselect_track(m_demuxer, m_audiotrackindex), "fail to mediademuxer_unselect_track for audio: "); }
>* demuxer ์ฌ์ฉ์ด ์๋ฃ๋๋ฉด unprepare, destroy๊ณผ์ ์ ํตํด ์ฌ์ฉํ๋ ๋ชจ๋ ์์์ ๋ฐํํ๋ค.
> ```{.c++}
void Demuxer::Destroy()
{
if(m_demuxer == NULL)
return;
for(int i= 0; i < m_tracks.size(); ++i)
{
if(m_tracks[i].info.fmt)
media_format_unref(m_tracks[i].info.fmt);
}
m_tracks.clear();
mediademuxer_unset_eos_cb(m_demuxer);
mediademuxer_unset_error_cb(m_demuxer);
//if ready state
mediademuxer_unprepare(m_demuxer);
mediademuxer_destroy(m_demuxer);
m_demuxer = NULL;
}
- Muxer ๊ตฌํ
class Muxer { public: Muxer(); ~Muxer(); void Create(const char* dstfilepath, mediamuxer_output_format_e format); void Destroy(); int AddTrack(media_format_h media_format); void CloseTrack(int track_index); void Start(); bool WriteSample(int track_index, media_packet_h sample); void Stop(); private: mediamuxer_h m_muxer; std::string m_dstfilename; };
>* Muxer๋ํ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๊ฒ๋ถํฐ ์์ํ๋ค. `Muxer::Create`์์๋ `mediamuxer_create`๋ฅผ ํตํด muxer๋ฅผ ์์ฑํ๊ณ , muxing ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํ format๊ณผ ํ์ผ ์ด๋ฆ์ `mediamuxer_set_data_sink`๋ก ์ค์ ํ๋ค. ์ฌ๊ธฐ์ ์ฃผ์ํ ์ ์ ํ์ผ ์ด๋ฆ์ local memory์ ๋ฃ์ด์ ๋๊ฒจ์ฃผ๋ฉด ์๋๋ค๋ ๊ฒ์ด๋ค. Platform์์ ๋ณ๋ memory๋ฅผ ํ ๋นํ์ง ์๊ธฐ ๋๋ฌธ์ ์ค๋์์ด ์ผ์ด๋๊ฒ ๋๋ค. Tizen3.0์์ ์ง์ํ๋ format์ ์๋์ ๊ฐ๋ค.
> ```{.c++}
typedef enum {
MEDIAMUXER_CONTAINER_FORMAT_MP4 = MEDIA_FORMAT_CONTAINER_MP4, /**< The mediamuxer output format is MP4 container */
MEDIAMUXER_CONTAINER_FORMAT_3GP = MEDIA_FORMAT_CONTAINER_3GP, /**< The mediamuxer output format is 3GP container */
MEDIAMUXER_CONTAINER_FORMAT_WAV = MEDIA_FORMAT_CONTAINER_WAV, /**< The mediamuxer output format is WAV container */
MEDIAMUXER_CONTAINER_FORMAT_AMR_NB = MEDIA_FORMAT_AMR_NB, /**< The mediamuxer output format is ARM_NB container */
MEDIAMUXER_CONTAINER_FORMAT_AMR_WB = MEDIA_FORMAT_AMR_WB, /**< The mediamuxer output format is AMR_WB container */
MEDIAMUXER_CONTAINER_FORMAT_AAC_ADTS = MEDIA_FORMAT_CONTAINER_AAC_ADTS /**< The mediamuxer output format is AAC_ADTS container */
} mediamuxer_output_format_e;
void Muxer::Create(const char* dstfilepath, mediamuxer_output_format_e format) { m_dstfilename = dstfilepath; iferror_throw(mediamuxer_create(&m_muxer), "fail to mediamuxer_create"); int ret = MEDIAMUXER_ERROR_NONE; if((ret = mediamuxer_set_data_sink(m_muxer, const_cast<char*>(m_dstfilename.c_str()), format))!= MEDIAMUXER_ERROR_NONE) { mediamuxer_destroy(m_muxer); m_muxer = NULL; throw std::runtime_error("fail to mediamuxer_set_data_sinkr"); } }
>* Muxer ์์ฑ์ ์ฑ๊ณตํ๋ฉด `mediamuxer_add_track`์ ํตํด video/audio track์ ์ถ๊ฐํ๊ณ track์ format ์ ๋ณด๋ฅผ ์๋ ค์ค์ผ ํ๋ค. format ์ ๋ณด๋ `media_format`ํจ์๋ค์ ํตํด์ ์๋์ ๊ฐ์ด ๋ง๋ค ์ ์๋ค. ๋ณธ sample app์์๋ video/audio encoder์์ format์ ์์ฑํ์ฌ ์ ๊ณตํ๋ค.
> ```{.c++}
//note that you should add error handling codes for each media_format function calls
media_format_h video_fmt;
int ret = media_format_create(&video_fmt);
ret = media_format_set_video_mime(video_fmt, MEDIA_FORMAT_H264_SP);
ret = media_format_set_video_width(video_fmt, 640);
ret = media_format_set_video_height(video_fmt, 480);
ret = media_format_set_video_avg_bps(video_fmt, 256000);
ret = media_format_set_video_max_bps(video_fmt, 256000);
int Muxer::AddTrack(media_format_h media_format) { int index = -1; iferror_throw(mediamuxer_add_track(m_muxer, media_format, &index), "fail to mediamuxer_add_track"); return index; }
>* track์ด ์ฑ๊ณต์ ์ผ๋ก ์ถ๊ฐ๋์๋ค๋ฉด muxing์ ์์ํ ์ ์๋ค. `mediamuxer_prepare`๋ก _MEDIA_MUXER_READY_STATE_ ๋ก ๋ง๋ค๊ณ ๋ฐ๋ก `mediamuxer_start`๋ก _RUNNING_ ์ํ๋ฅผ ๋ง๋ค์. Media Demuxer์ ๋ค๋ฅด๊ฒ Muxer์์๋ _MEDIA_MUXER_READY_STATE_ ์์ ํ ์ ์๋ ์์
์ด start์ธ์๋ ์๋ค. `mediamuxer_prepare`์ฑ๊ณตํ๋ค๋ฉด ๋ฐ๋ก `mediamuxer_start`๋ฅผ ํธ์ถํ๋ฉด ๋๊ฒ ๋ค.
> ```{.c++}
void Muxer::Start()
{
iferror_throw(mediamuxer_prepare(m_muxer), "fail to mediamuxer_prepare");
int ret = MEDIAMUXER_ERROR_NONE;
if((ret = mediamuxer_start(m_muxer))!= MEDIAMUXER_ERROR_NONE)
{
mediamuxer_unprepare(m_muxer);
m_muxer = NULL;
throw std::runtime_error("fail to mediamuxer_start");
}
}
- MEDIA_MUXER_RUNNING_STATE ์์ packet์ writeํ ์ ์๋ค. Muxer๋ video/audio encoding์ ํ์ง ์๊ณ container format์ ๋ง์ถฐ์ด multiplexing๋ง ์ํํ๋ค. Media Codec์ ์ฌ์ฉํด์ encoding ๋ packet ์ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
bool Muxer::WriteSample(int track_index, media_packet_h sample) { int ret = MEDIAMUXER_ERROR_NONE; if((ret = mediamuxer_write_sample(m_muxer, track_index, sample))!= MEDIAMUXER_ERROR_NONE) { dlog_print(DLOG_ERROR, "Muxer", "fail to mediamuxer_write_sample:%d", ret); return false; } return true; }
>* packet write ๊ฐ ์๋ฃ๋๋ฉด ์์ฑ ์์์ ์ญ์์ผ๋ก resource๋ฅผ ์ ๋ฆฌํด์ผ ํ๋ค. `mediamuxer_stop`-> `mediamuxer_unprepare` -> `mediamuxer_destroy`
* Codec ๊ตฌํ
> [codecbase.h](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/inc/codecbase.h)
> [codecbase.cpp](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/src/codecbase.cpp)
> [videodecoder.h](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/inc/videodecoder.h)
> [videodecoder.cpp](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/src/videodecoder.cpp)
> [videoencoder.h](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/inc/videoencoder.h)
> [videoencoder.cpp](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/src/videoencoder.cpp)
> [audiodecoder.h](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/inc/audiodecoder.h)
> [audiodecoder.cpp](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/src/audiodecoder.cpp)
> [audioencoder.h](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/inc/audioencoder.h)
> [audioencoder.cpp](https://github.com/hereforu/tizen-multimedia-apps/blob/master/transcoder/src/audioencoder.cpp)
>* Codec์ [tizen media codec](https://developer.tizen.org/development/api-references/native-application?redirect=/dev-guide/2.3.1/org.tizen.native.mobile.apireference/group__CAPI__MEDIA__CODEC__MODULE.html)์ ํ์ฉํ์ฌ ๊ตฌํ๋์์ผ๋ฉฐ ์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด CodecBase๋ฅผ base class๋ก ํ์ฌ video/audio encoder/decoder๋ฅผ ๋ง๋๋ ๊ตฌ์กฐ๋ก ๊ตฌํํ์๋ค.
> 
> ```{.c++}
class CodecBase
{
public:
CodecBase();
virtual ~CodecBase();
void Create(const CodecInfo& codecinfo);
void Destroy();
bool GetPacket(media_packet_h& packet);
bool InsertPacket(media_packet_h packet);
bool IsEoS();
...
};
- Codec์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ์ค์ ํ๋ ๊ฒ๋ถํฐ Demuxer/Muxer ๋ณด๋ค ๋ณต์กํ๋ค.
- ์ผ๋จ
mediacodec_create
๋ก codec์ ์์ฑํ๊ณ handle์ ๋๊ฒจ ๋ฐ๋๋ค.
mediacodec_set_codec
์ผ๋ก codec์ ์ค์ ํ๋ค. Tizen code ์ ์ํ๋ฉด ์ค์ ๊ฐ๋ฅํ codec ์ ์ฌ๊ธฐ์ ํ์ธํ์.
- ์ค์ ํ codec์ ๋ง๋ ์ธ๋ถ์ ๋ณด๋ฅผ ์ค์ ํ๋ค. audio decoder๋ผ๋ฉด
mediacodec_set_adec_info
๋ฅผ ์ฌ์ฉํด ์ธ๋ถ ์ ๋ณด๋ฅผ ์ค์ ํ๊ณ , audio encder๋mediacodec_set_aenc_info
, video decoder๋mediacodec_set_vdec_info
, video encoder๋mediacodec_set_venc_info
๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
- ํ์ํ callback ํจ์๋ค์ ์ค์ ํ๋ค.
mediacodec_set_eos_cb
์mediacodec_set_error_cb
๋ ๊ฐ๊ฐ eos์ error ์๋ฆผ์ ๋ฐ๊ธฐ ์ํด ์ฌ์ฉํ๋ค.mediacodec_set_input_buffer_used_cb
๋ input์ผ๋ก ๋ฃ์ด์ค packet์ ์ฌ์ฉ์ด ์๋ฃ๋๋ฉด ํธ์ถ๋๋ callback์ ์ค์ ํ๋ ํจ์์ด๋ค. ํจ์จ์ ์ธ memory ์ฌ์ฉ์ ์ํด ์ฌ๊ธฐ์ ์ฌ์ฉ ์๋ฃ๋ packet์ destroyํ๋ค. encoding/decoding๋ ๊ฒฐ๊ณผ๋mediacodec_set_output_buffer_available_cb
๋ฅผ ํตํด ์ค์ ํ callbackํจ์๋ฅผ ํตํด ๋์ด์จ๋ค. ์ฃผ์ํ ์ ์ callback ํจ์์ parameter๋ก ๋์ด์จ packet์ด ๊ฒฐ๊ณผ packet์ด ์๋๋ผ๋ ๊ฒ์ด๋ค. callback์ด ํธ์ถ๋ ์์ ์mediacodec_get_output
์ ํตํด ์ป์ด์์ผ ํ๋ค. ์ ์ด๋ ๊ฒ ๋์ด ์๋์ง๋ ์์ง ๋ด๊ณต์ด ๋ถ์กฑํด์...
- prepare๊น์ง ํ๋ฉด packet์ ์ง์ด ๋ฃ์ด encoding/decodingํ ์ค๋น๊ฐ ์๋ฃ๋๋ค.
//callback functions declaration static void mc_input_buffer_used_cb(media_packet_h pkt, void *user_data); static void mc_output_buffer_available_cb(media_packet_h pkt, void *user_data); static void mc_error_cb(mediacodec_error_e error, void *user_data); static void mc_eos_cb(void *user_data);
> ```{.c++}
typedef struct _VDecInfo
{
mediacodec_codec_type_e codecid;
int width;
int height;
}VDecInfo;
typedef struct _VEncInfo
{
mediacodec_codec_type_e codecid;
int width;
int height;
int fps;
int target_bits;
}VEncInfo;
typedef struct _ADecInfo
{
mediacodec_codec_type_e codecid;
int samplerate;
int channel;
int bit;
}ADecInfo;
typedef struct _AEncInfo
{
mediacodec_codec_type_e codecid;
int samplerate;
int channel;
int bit;
int bitrate;
}AEncInfo;
typedef union _CodecInfo
{
VDecInfo vdec;
VEncInfo venc;
ADecInfo adec;
AEncInfo aenc;
}CodecInfo;
void CodecBase::Create(const CodecInfo& codecinfo)
{
iferror_throw(mediacodec_create(&m_mediacodec), "fail to create mediacodec_create");
if(create(m_mediacodec, codecinfo) == false)//this function should be defined in each derived class
throw_error_and_destroy_codec(m_mediacodec, "fail to create specific codec", 0)
int ret = MEDIACODEC_ERROR_NONE;
if((ret = mediacodec_set_eos_cb(m_mediacodec, mc_eos_cb, (void*)this)) != MEDIACODEC_ERROR_NONE)
throw_error_and_destroy_codec(m_mediacodec, "fail to mediacodec_set_eos_cb", ret);
if((ret = mediacodec_set_error_cb(m_mediacodec, mc_error_cb, (void*)this)) != MEDIACODEC_ERROR_NONE)
throw_error_and_destroy_codec(m_mediacodec, "fail to mediacodec_set_error_cb", ret);
if((ret = mediacodec_set_input_buffer_used_cb(m_mediacodec, mc_input_buffer_used_cb, (void*)this)) != MEDIACODEC_ERROR_NONE)
throw_error_and_destroy_codec(m_mediacodec, "fail to mediacodec_set_input_buffer_used_cb", ret);
if((ret = mediacodec_set_output_buffer_available_cb(m_mediacodec, mc_output_buffer_available_cb, (void*)this)) != MEDIACODEC_ERROR_NONE)
throw_error_and_destroy_codec(m_mediacodec, "fail to mediacodec_set_output_buffer_available_cb", ret);
if((ret = mediacodec_prepare(m_mediacodec)) != MEDIACODEC_ERROR_NONE)
throw_error_and_destroy_codec(m_mediacodec, "fail to mediacodec_prepare", ret);
}
//an example of specific codec creation bool VideoDecoder::create(mediacodec_h mediacodec, const CodecInfo& codecinfo) { int ret = MEDIACODEC_ERROR_NONE; if((ret = mediacodec_set_codec(mediacodec, codecinfo.vdec.codecid, MEDIACODEC_DECODER|MEDIACODEC_SUPPORT_TYPE_SW)) != MEDIACODEC_ERROR_NONE) { dlog_print(DLOG_ERROR, "CodecBase", "fail to mediacodec_set_codec [%d]", ret); return false; } if((ret = mediacodec_set_vdec_info(mediacodec, codecinfo.vdec.width, codecinfo.vdec.height)) != MEDIACODEC_ERROR_NONE) { dlog_print(DLOG_ERROR, "CodecBase", "fail to mediacodec_set_vdec_info [%d]", ret); return false; } return true; }
>* encoding/decodingํ packet์ ์์๋๋ก ๋ฐ์ด ๋ฃ๋๋ค.
> ```{.c++}
bool CodecBase::InsertPacket(media_packet_h packet)
{
int ret = mediacodec_process_input(m_mediacodec, packet, 1000);
if(ret != MEDIACODEC_ERROR_NONE)
{
dlog_print(DLOG_ERROR, "CodecBase", "fail to mediacodec_process_input %d [%s]", ret, getname());
return false;
}
return true;
}
- ์ด์ eos ๋๋ error๊ฐ ๋ฐ์ํ ๋๊น์ง ์์ฑ์ ์ค์ ํ
mc_input_buffer_used_cb
,mc_output_buffer_available_cb
callback ํจ์๋ฅผ handling ํ๋ฉด ๋๋ค.
์ ์ ํ ๋ฐ์ ๊ฐ์ด
mc_input_buffer_used_cb
๋ input์ผ๋ก ๋ฃ์ด์ค packet์ด codec ๊ฐ์ฒด์์ ๋์ด์ ์ฌ์ฉ๋์ง ์์ผ๋ฉด ํธ์ถ๋๋ค. callback์ด ํธ์ถ๋๋ฉด packet์ destroy ํ๋ค.
void CodecBase::handle_input_buffer_used(media_packet_h pkt) { media_packet_destroy(pkt); } //callback function void CodecBase::mc_input_buffer_used_cb(media_packet_h pkt, void user_data) { CodecBase codec = (CodecBase*)user_data; codec->handle_input_buffer_used(pkt); }
>> `mc_output_buffer_available_cb`๋ ๊ฒฐ๊ณผ packet์ด ๋ง๋ค์ด์ง๋ฉด ํธ์ถ๋๋ค. packet์ ํ๋ฃํ์ฌ ์ ์ ํ ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด ๋๋ค. ๋ณธ sample code์์๋ output queue๋ฅผ ๋์ด queue์ ๊ฒฐ๊ณผ ํ์ผ์ insertํ๋ค.
>> ```{.c++}
void CodecBase::handle_output_buffer_available(media_packet_h pkt)
{
media_packet_h output_buf = NULL;
int ret = mediacodec_get_output(m_mediacodec, &output_buf, 0);
if(ret != MEDIACODEC_ERROR_NONE)
{
dlog_print(DLOG_ERROR, "CodecBase", "fail to mediacodec_get_output %d [%s]", ret, getname());
}
pushpacket_to_outputqueue(output_buf);
}
//callback function
void CodecBase::mc_output_buffer_available_cb(media_packet_h pkt, void *user_data)
{
CodecBase* codec = (CodecBase*)user_data;
codec->handle_output_buffer_available(pkt);
}
- encoding/decoding์ด ์๋ฃ๋๋ฉด
mediacodec_unprepare
ํ๊ณmediacodec_destroy
๋ฅผ ํธ์ถํด์ ์์์ ๋ฐํํ๋ค.
transcodingengine.h transcodingengine.cpp
class TranscodingEngine { public: enum { DEMUX_COUNTER = 0, DECODE_COUNTER, ENCODE_COUNTER, MAX_COUNTER }; enum { VIDEO_DECODER = 0, VIDEO_ENCODER, AUDIO_DECODER, AUDIO_ENCODER, MAX_CODEC, }; TranscodingEngine(); ~TranscodingEngine(); void Destroy(); void Transcoding(const char* srcfilename, unsigned int duration, const CodecInfo& venc, const CodecInfo& aenc); void Cancel(); double GetProgress(); const char* GetDstFileName(); bool IsCanceled(); private: ... private: Demuxer* m_demuxer; Muxer* m_muxer; ImageResizer* m_resizer; CodecBase* m_codec[MAX_CODEC]; ... };
>* TranscodingEngine class๋ ์ง๊ธ๊น์ง ์ค๋ช
ํ demuxer, muxer, codec, ๊ทธ๋ฆฌ๊ณ image transform ๊ธฐ๋ฅ์ ๋ชจ๋ ์ฌ์ฉํ์ง๋ง ์ธ๋ถ์ Transcoding๊ณผ Cancel๋ง ์ ๊ณตํ๋ค.
>* Transcoding์ ์์ํ๊ธฐ ์ํด์๋ transcodingํ ํ์ผ ์ด๋ฆ `srcfilename`๊ณผ video/audio encoder ์ ๋ณด์ธ `CodecInfo venc/aenc`๋ฅผ ์ค์ ํด์ผ ํ๋ค. video/audio encoder ์ ๋ณด๋ ๋ค์๊ณผ ๊ฐ๋ค.
> ```{.c++}
typedef struct _VEncInfo
{
mediacodec_codec_type_e codecid;
int width;
int height;
int fps;
int target_bits;
}VEncInfo;
typedef struct _AEncInfo
{
mediacodec_codec_type_e codecid;
int samplerate;
int channel;
int bit;
int bitrate;
}AEncInfo;
- Transcoding ํจ์์์๋ ์ ์ ๋ณด๋ค์ ๋ฐ์ demuxer, muxer, codec, image_resizer ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์ด ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด run-time exception์ ๋ฐ์์ํค์ด Transcoding์ ์ข ๋ฃํ๋ค. Transcoding ํจ์์์๋ prepareํจ์๋ฅผ ํธ์ถํ์ฌ ํ์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
void TranscodingEngine::prepare(const char* srcfilename, unsigned int duration, const CodecInfo& venc, const CodecInfo& aenc) { m_dstfilename = generatedstfilename(srcfilename); create_demuxer(srcfilename); CodecInfo option[MAX_CODEC]; option[VIDEO_ENCODER] = venc; option[AUDIO_ENCODER] = aenc; fill_and_get_codec_info(option[VIDEO_DECODER], option[AUDIO_DECODER], option[VIDEO_ENCODER], option[AUDIO_ENCODER]); for(int i = 0; i < MAX_CODEC; ++i) create_codec(i, option[i]); create_muxer(srcfilename); if(option[VIDEO_ENCODER].venc.width != option[VIDEO_DECODER].vdec.width || option[VIDEO_ENCODER].venc.height != option[VIDEO_DECODER].vdec.height) create_resizer(option[VIDEO_ENCODER].venc.width, option[VIDEO_ENCODER].venc.height); }
>>* `generatedstfilename` ํจ์๋ source ํ์ผ ์ด๋ฆ์ ๋ง์ง๋ง์ _trans๋ฅผ ๋ถ์ฌ source ํ์ผ๊ณผ ๊ฐ์ ํด๋์ ๊ฒฐ๊ณผ ํ์ผ์ด ๋ง๋ค์ด์ง ์ ์๋๋ก ์์ฑํ๋ค. ๋ง์ผ ๋์ผ ํ์ผ์ด ์ด๋ฏธ ์กด์ฌํ๋ฉด _trans๋ค๋ก ์ผ๋ จ ๋ฒํธ๋ฅผ ๋ถ์ด๋๋ก ์์ฑํ์๋ค.
>>* `create_demuxer`๋ ์ด๋ฏธ ์ค๋ช
ํ demuxer ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฉค๋ฒ ๋ณ์์ ํ ๋นํ๋ ํจ์์ด๋ค. ์์ฑ ์ค๊ฐ์ exception์ด ๋ฐ์ํด๋ memory leak์ด ๋ฐ์ํ์ง ์๋๋ก auto_ptr๋ก ์์ฑํ๋ค. exception์ด ๋ฐ์ํ์ง ์์ผ๋ฉด auto_ptr์ ํด์ ํ๊ณ ์์ฑ๋ ๊ฐ์ฒด๋ฅผ member ๋ณ์์ ํ ๋นํ์ฌ ์์ฑ์ ์๋ฃํ๋ค.
>> ```{.c++}
void TranscodingEngine::create_demuxer(const char* srcfilename)
{
std::auto_ptr<Demuxer> demuxer(new Demuxer);
demuxer->Create(srcfilename);
demuxer->ExtractTrackinfo();
m_demuxer = demuxer.release();
}
create_muxer
,create_resizer
,create_codec
๋ชจ๋ ์ด์ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ํ์ฉ ์ ๋จ๊ณ๊น์ง ์ด๊ธฐํํ์ฌ ์ค๋นํ๋ค.
void TranscodingEngine::create_codec(int codectype, const CodecInfo& codecinfo) { CodecBase* p = NULL; switch(codectype) { case VIDEO_DECODER: p = (CodecBase*)new VideoDecoder; break; case VIDEO_ENCODER: p = (CodecBase*)new VideoEncoder; break; case AUDIO_DECODER: p = (CodecBase*)new AudioDecoder; break; case AUDIO_ENCODER: p = (CodecBase*)new AudioEncoder; break; default: throw std::runtime_error("invalid codec type"); break; } std::auto_ptr codec(p); codec->Create(codecinfo); m_codec[codectype] = codec.release(); }
>> ```{.c++}
void TranscodingEngine::create_muxer(const char* srcfilename)
{
std::auto_ptr<Muxer> muxer(new Muxer);
muxer->Create(generatedstfilename(srcfilename), MEDIAMUXER_CONTAINER_FORMAT_MP4);
m_muxer_video_track_index = muxer->AddTrack(m_codec[VIDEO_ENCODER]->GetMediaFormat());
m_muxer_audio_track_index = muxer->AddTrack(m_codec[AUDIO_ENCODER]->GetMediaFormat());
m_muxer = muxer.release();
}
void TranscodingEngine::create_resizer(int target_width, int target_height) { std::auto_ptr resizer(new ImageResizer); resizer->Create(target_width, target_height); m_resizer = resizer.release(); }
>* ํ์ ๊ฐ์ฒด ์ค๋น๊ฐ ์๋ฃ๋๋ฉด demuxer์ muxer๋ฅผ start ์ํค๊ณ transcoding์ ์งํํ๋ค. ๋ชจ๋ packet์ ๋ํ ์ฒ๋ฆฌ๊ฐ ๋๋๋ฉด muxer์ demuxer๋ฅผ stopํ๊ณ transcoding์ ์๋ฃํ๋ค.
> ```{.c++}
void TranscodingEngine::Transcoding(const char* srcfilename, unsigned int duration, const CodecInfo& venc, const CodecInfo& aenc)
{
device_power_request_lock(POWER_LOCK_CPU, 0);
prepare(srcfilename, duration, venc, aenc);
m_demuxer->Start();
m_muxer->Start();
transcoding();
m_muxer->Stop();
m_demuxer->Stop();
unprepare();
device_power_release_lock(POWER_LOCK_CPU);
}
- transcoding ํจ์๋ ๊ฐ track์ ๋ํด์ packet ๊ฐ๊ฐ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ PTS(Presentaion Time Stamp)๋ฅผ ๊ณ ๋ คํ์ฌ ์งํํ๋ค. PTS๋ฅผ ๊ณ ๋ คํ์ง ์๊ณ ์งํํ๋ฉด demuxer ๋ด๋ถ์์ buffering ๋ packet์ด ๋ง์์ ธ ์๋๊ฐ ๋๋ ค์ง๊ฑฐ๋ hang์ด ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก ์ฃผ์ํ๋ค. ๋ํ ๋งค iteration๋ง๋ค sleep์ ๋ฃ์ด ์ฃผ์ด demuxer, muxer, codec ์ด ์ํ ํ ์คํ๋ ์ ์๋๋ก ํด์ค๋ค.
void TranscodingEngine::transcoding() { int video_counter[MAX_COUNTER] = {0,}; //to represent the progress int audio_counter[MAX_COUNTER] = {0,}; int iteration = 0; unsigned int video_pts = 0, audio_pts = 0; m_bcanceled = false; while(!m_bcanceled) { process_track(m_demuxer->GetVideoTrackIndex(), m_muxer_video_track_index, m_codec[VIDEO_DECODER], m_codec[VIDEO_ENCODER], video_counter, video_pts); do { process_track(m_demuxer->GetAudioTrackIndex(), m_muxer_audio_track_index, m_codec[AUDIO_DECODER], m_codec[AUDIO_ENCODER], audio_counter, audio_pts); }while(audio_pts <= video_pts && m_codec[AUDIO_ENCODER]->IsEoS()==false); if(m_codec[VIDEO_ENCODER]->IsEoS() == true && m_codec[AUDIO_ENCODER]->IsEoS() == true) { break; } m_progress_count = video_counter[ENCODE_COUNTER]; usleep(500000); } m_muxer->CloseTrack(m_muxer_video_track_index); m_muxer->CloseTrack(m_muxer_audio_track_index); usleep(1000000); }
>>* ๊ฐ track์ ๋ํ ์ฒ๋ฆฌ๋ ์๋์ ๊ฐ์ด ์งํ๋๋ค.
>>>* `feed_decoder_with_packet`: demuxer๋ก๋ถํฐ encoded packet์ ์ฝ์ด์ decoder์ input queue์ ๋ฃ์ด์ค๋ค.
>>> ```{.c++}
bool TranscodingEngine::feed_decoder_with_packet(CodecBase* decoder, int track_index, int& count, unsigned int& pts)
{
media_packet_h demux_packet = NULL;
if(m_demuxer->IsEoS(track_index) == true)
return true;
if(m_demuxer->ReadSeample(track_index, &demux_packet))
{
++count;
pts = get_pts_in_msec(demux_packet);
dlog_print(DLOG_DEBUG, "TranscodingEngine", "%dth demuxed packet [pts:%u]", count, pts);
if(!decoder->InsertPacket(demux_packet))
{
dlog_print(DLOG_ERROR, "TranscodingEngine", "while putting the %d th packet to the decoder", count);
return false;
}
}
return true;
}
feed_encoder_with_packet
: decoder๋ก๋ถํฐ decoding๋ packet์ ์ฝ์ด์ encoder์ input queue์ ๋ฃ์ด์ค๋ค. image์ ๊ฒฝ์ฐ ํ์ํ๋ค๋ฉด image transform์ ํ์ฉํ์ฌ resizing ํด์ค๋ค.
bool TranscodingEngine::feed_encoder_with_packet(CodecBase* decoder, CodecBase* encoder, int& count) { media_packet_h decoded_packet = NULL; if(decoder->IsEoS()==true) return true; if(decoder->GetPacket(decoded_packet)) { ++count; dlog_print(DLOG_DEBUG, "TranscodingEngine", "%dth decoded packet", count); if(m_resizer) //there will be no resizer object if the resolutions of a source and a target video are identical { if(resize_resolution_if_image(&decoded_packet)==false) { dlog_print(DLOG_ERROR, "TranscodingEngine", "while resizing the %d th packet", count); return false; } } if(encoder->InsertPacket(decoded_packet)==false) { dlog_print(DLOG_ERROR, "TranscodingEngine", "while putting the %d th packet to the encoder", count); return false; } } return true; }
>>>* `feed_muxer_with_packet`: encoder๋ก๋ถํฐ encoding๋ packet์ ์ฝ์ด์ muxer๋ฅผ ํตํด writeํ๋ค.
>>> ```{.c++}
bool TranscodingEngine::feed_muxer_with_packet(CodecBase* encoder, int muxer_track_index, int& count)
{
bool bret = true;
if(encoder == NULL)
return false;
media_packet_h encoded_packet;
if(encoder->IsEoS()==false)
{
if(encoder->GetPacket(encoded_packet))
{
++count;
bret = m_muxer->WriteSample(muxer_track_index, encoded_packet);
media_packet_destroy(encoded_packet);
dlog_print(DLOG_DEBUG, "TranscodingEngine", "%dth encoded packet", count);
}
}
return bret;
}
- ์ ๊ณผ์ ์ค ํ๋๋ผ๋ ์คํจํ๋ฉด process_track์ exception์ ๋ฐ์์ํค๊ณ transcoding์ ์ข ๋ฃํ๋ค.
void TranscodingEngine::process_track(int track_index, int muxer_track_index, CodecBase* decoder, CodecBase* encoder, int counter[], unsigned int& pts) { if(encoder->IsEoS()==true) return; if(feed_decoder_with_packet(decoder, track_index, counter[DEMUX_COUNTER], pts)==false) { throw std::runtime_error("fail to feed_decoder_with_packet"); } if(feed_encoder_with_packet(decoder, encoder, counter[DECODE_COUNTER])==false) { throw std::runtime_error("fail to feed_encoder_with_packet"); } if(feed_muxer_with_packet(encoder, muxer_track_index, counter[ENCODE_COUNTER])==false) { throw std::runtime_error("fail to feed_muxer_with_packet"); } }