diff --git a/README.md b/README.md
index 9102b8e22..75270d32d 100755
--- a/README.md
+++ b/README.md
@@ -50,6 +50,7 @@ url: rtmp://127.0.0.1:1935/live/livestream
 * nginx v1.5.0: 139524 lines 
 
 ### History
+* v0.5, 2013-11-24, support write ts file.
 * v0.5, 2013-11-21, add ts_info tool to demux ts file.
 * v0.5, 2013-11-16, add rtmp players(OSMF/jwplayer5/jwplayer6).
 * v0.4, 2013-11-10, v0.4 released. 12500 lines.
diff --git a/trunk/configure b/trunk/configure
old mode 100644
new mode 100755
diff --git a/trunk/src/core/srs_core_codec.cpp b/trunk/src/core/srs_core_codec.cpp
index 05d2ce41c..fb15c9331 100644
--- a/trunk/src/core/srs_core_codec.cpp
+++ b/trunk/src/core/srs_core_codec.cpp
@@ -24,12 +24,35 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #include 
 
 #include 
+#include 
 
 #include 
 #include 
 #include 
 #include 
 
+SrsCodecBuffer::SrsCodecBuffer()
+{
+	size = 0;
+	bytes = NULL;
+}
+
+void SrsCodecBuffer::append(void* data, int len)
+{
+	srs_assert(data);
+	srs_assert(len > 0);
+	
+	bytes = (char*)realloc(bytes, size + len);
+	memcpy(bytes + size, data, len);
+	size += len;
+}
+
+void SrsCodecBuffer::free()
+{
+	size = 0;
+	srs_freepa(bytes);
+}
+
 SrsCodecSample::SrsCodecSample()
 {
 	clear();
@@ -83,8 +106,11 @@ SrsCodec::SrsCodec()
 	video_codec_id 			= 0;
 	audio_data_rate 		= 0;
 	audio_codec_id 			= 0;
-	profile 				= 0;
-	level 					= 0;
+	avc_profile 			= 0;
+	avc_level 				= 0;
+	aac_profile				= 0;
+	aac_sample_rate			= 0;
+	aac_channels			= 0;
 	avc_extra_size 			= 0;
 	avc_extra_data 			= NULL;
 	aac_extra_size 			= 0;
@@ -160,6 +186,39 @@ int SrsCodec::audio_aac_demux(int8_t* data, int size, SrsCodecSample* sample)
 			aac_extra_data = new char[aac_extra_size];
 			memcpy(aac_extra_data, stream->current(), aac_extra_size);
 		}
+		
+		// only need to decode the first 2bytes:
+		// audioObjectType, aac_profile, 5bits.
+		// samplingFrequencyIndex, aac_sample_rate, 4bits.
+		// channelConfiguration, aac_channels, 4bits
+		if (!stream->require(2)) {
+			ret = ERROR_HLS_DECODE_ERROR;
+			srs_error("hls decode audio aac sequence header failed. ret=%d", ret);
+			return ret;
+		}
+		aac_profile = stream->read_1bytes();
+		aac_sample_rate = stream->read_1bytes();
+		
+		aac_channels = (aac_sample_rate >> 3) & 0x0f;
+		aac_sample_rate = ((aac_profile << 1) & 0x0e) | ((aac_sample_rate >> 7) & 0x01);
+		aac_profile = (aac_profile >> 3) & 0x1f;
+		
+		if (aac_profile == 0 || aac_profile == 0x1f) {
+			ret = ERROR_HLS_DECODE_ERROR;
+			srs_error("hls decode audio aac sequence header failed, "
+				"adts object=%d invalid. ret=%d", aac_profile, ret);
+			return ret;
+		}
+		
+		// aac_profile = audioObjectType - 1
+		aac_profile--;
+		
+		if (aac_profile > 3) {
+			// Mark all extended profiles as LC
+			// to make Android as happy as possible.
+			// @see: ngx_rtmp_hls_parse_aac_header
+			aac_profile = 1;
+		}
 	} else if (aac_packet_type == SrsCodecAudioTypeRawData) {
 		// ensure the sequence header demuxed
 		if (aac_extra_size <= 0 || !aac_extra_data) {
diff --git a/trunk/src/core/srs_core_codec.hpp b/trunk/src/core/srs_core_codec.hpp
index de576c7d3..17d1e2d49 100644
--- a/trunk/src/core/srs_core_codec.hpp
+++ b/trunk/src/core/srs_core_codec.hpp
@@ -181,8 +181,21 @@ enum SrsCodecAudioSoundType
 */
 struct SrsCodecBuffer
 {
+	/**
+	* @remark user must manage the bytes.
+	*/
 	int size;
 	char* bytes;
+	
+	SrsCodecBuffer();
+	void append(void* data, int len);
+	
+	/**
+	* free the bytes, 
+	* user can invoke it to free the bytes,
+	* the SrsCodecBuffer never free automatically.
+	*/
+	void free();
 };
 
 /**
@@ -227,9 +240,9 @@ public:
 	// @see: SrsCodecVideo
 	int			video_codec_id;
 	// profile_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45.
-	u_int8_t	profile; 
+	u_int8_t	avc_profile; 
 	// level_idc, H.264-AVC-ISO_IEC_14496-10.pdf, page 45.
-	u_int8_t	level; 
+	u_int8_t	avc_level; 
 	int			width;
 	int			height;
 	int			video_data_rate; // in bps
@@ -243,6 +256,13 @@ public:
 	// @see: SrsCodecAudioType
 	int			audio_codec_id;
 	int			audio_data_rate; // in bps
+	// 1.6.2.1 AudioSpecificConfig, in aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33.
+	// audioObjectType, value defines in 7.1 Profiles, aac-iso-13818-7.pdf, page 40.
+	u_int8_t	aac_profile; 
+	// samplingFrequencyIndex
+	u_int8_t	aac_sample_rate;
+	// channelConfiguration
+	u_int8_t	aac_channels;
 	// the avc extra data, the AVC sequence header,
 	// without the flv codec header,
 	// @see: ffmpeg, AVCodecContext::extradata
diff --git a/trunk/src/core/srs_core_error.hpp b/trunk/src/core/srs_core_error.hpp
index 710665350..5a6bf602c 100644
--- a/trunk/src/core/srs_core_error.hpp
+++ b/trunk/src/core/srs_core_error.hpp
@@ -112,5 +112,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #define ERROR_HLS_BUSY					602
 #define ERROR_HLS_OPEN_FAILED			603
 #define ERROR_HLS_WRITE_FAILED			604
+#define ERROR_HLS_AAC_FRAME_LENGTH		605
+#define ERROR_HLS_AVC_SAMPLE_SIZE		606
 
 #endif
\ No newline at end of file
diff --git a/trunk/src/core/srs_core_hls.cpp b/trunk/src/core/srs_core_hls.cpp
index 9c1624e58..69f3637b2 100644
--- a/trunk/src/core/srs_core_hls.cpp
+++ b/trunk/src/core/srs_core_hls.cpp
@@ -26,6 +26,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #include 
 #include 
 #include 
+#include 
+#include 
 
 #include 
 #include 
@@ -275,18 +277,249 @@ u_int8_t mpegts_header[] = {
     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
 };
 
+// @see: NGX_RTMP_HLS_DELAY, 700ms, ts_tbn=90000
+#define SRS_HLS_DELAY 63000
+
+// @see: ngx_rtmp_mpegts.c
+// TODO: support full mpegts feature in future.
+class SrsMpegtsWriter
+{
+public:
+	static int write_header(int fd)
+	{
+		int ret = ERROR_SUCCESS;
+		
+		if (::write(fd, mpegts_header, sizeof(mpegts_header)) != sizeof(mpegts_header)) {
+			ret = ERROR_HLS_WRITE_FAILED;
+			srs_error("write ts file header failed. ret=%d", ret);
+			return ret;
+		}
+
+		return ret;
+	}
+	static int write_frame(int fd, mpegts_frame* frame, SrsCodecBuffer* buffer)
+	{
+		int ret = ERROR_SUCCESS;
+		
+		char* last = buffer->bytes + buffer->size;
+		char* pos = buffer->bytes;
+		
+		bool first = true;
+		while (pos < last) {
+			static char packet[188];
+			char* p = packet;
+			
+			frame->cc++;
+			
+			// sync_byte; //8bits
+			*p++ = 0x47;
+			// pid; //13bits
+			*p++ = (frame->pid >> 8) & 0x1f;
+			// payload_unit_start_indicator; //1bit
+			if (first) {
+				p[-1] |= 0x40;
+			}
+			*p++ = frame->pid;
+			
+			// transport_scrambling_control; //2bits
+			// adaption_field_control; //2bits, 0x01: PayloadOnly
+			// continuity_counter; //4bits
+			*p++ = 0x10 | (frame->cc & 0x0f);
+			
+			if (first) {
+				first = false;
+				if (frame->key) {
+					p[-1] |= 0x20; // Both Adaption and Payload
+					*p++ = 7;    // size
+					*p++ = 0x50; // random access + PCR
+					p = write_pcr(p, frame->dts - SRS_HLS_DELAY);
+				}
+				
+				// PES header
+				// packet_start_code_prefix; //24bits, '00 00 01'
+				*p++ = 0x00;
+				*p++ = 0x00;
+				*p++ = 0x01;
+				//8bits
+				*p++ = frame->sid;
+				
+				// pts(33bits) need 5bytes.
+				u_int8_t header_size = 5;
+				u_int8_t flags = 0x80; // pts
+				
+				// dts(33bits) need 5bytes also
+				if (frame->dts != frame->pts) {
+					header_size += 5;
+					flags |= 0x40; // dts
+				}
+				
+				// 3bytes: flag fields from PES_packet_length to PES_header_data_length
+				int pes_size = (last - pos) + header_size + 3;
+				if (pes_size > 0xffff) {
+					/**
+					* when actual packet length > 0xffff(65535),
+					* which exceed the max u_int16_t packet length,
+					* use 0 packet length, the next unit start indicates the end of packet.
+					*/
+					pes_size = 0;
+				}
+				
+				// PES_packet_length; //16bits
+				*p++ = (pes_size >> 8);
+				*p++ = pes_size;
+				
+				// PES_scrambling_control; //2bits, '10'
+				// PES_priority; //1bit
+				// data_alignment_indicator; //1bit
+				// copyright; //1bit
+				// original_or_copy; //1bit	
+				*p++ = 0x80; /* H222 */
+				
+				// PTS_DTS_flags; //2bits
+				// ESCR_flag; //1bit
+				// ES_rate_flag; //1bit
+				// DSM_trick_mode_flag; //1bit
+				// additional_copy_info_flag; //1bit
+				// PES_CRC_flag; //1bit
+				// PES_extension_flag; //1bit
+				*p++ = flags;
+				
+				// PES_header_data_length; //8bits
+				*p++ = header_size;
+
+				// pts; // 33bits
+				p = write_pts(p, flags >> 6, frame->pts + SRS_HLS_DELAY);
+				
+				// dts; // 33bits
+				if (frame->dts != frame->pts) {
+					p = write_pts(p, 1, frame->dts + SRS_HLS_DELAY);
+				}
+			}
+			
+			int body_size = sizeof(packet) - (p - packet);
+			int in_size = last - pos;
+			
+			if (body_size <= in_size) {
+				memcpy(p, pos, body_size);
+				pos += body_size;
+			} else {
+				p = fill_stuff(p, packet, body_size, in_size);
+				memcpy(p, pos, in_size);
+				pos = last;
+			}
+			
+			// write ts packet
+			if (::write(fd, packet, sizeof(packet)) != sizeof(packet)) {
+				ret = ERROR_HLS_WRITE_FAILED;
+				srs_error("write ts file failed. ret=%d", ret);
+				return ret;
+			}
+		}
+		
+		// write success, clear and free the buffer
+		buffer->free();
+		
+		return ret;
+	}
+private:
+	static char* fill_stuff(char* pes_body_end, char* packet, int body_size, int in_size)
+	{
+		char* p = pes_body_end;
+		
+		// insert the stuff bytes before PES body
+		int stuff_size = (body_size - in_size);
+		
+		// adaption_field_control; //2bits
+		if (packet[3] & 0x20) {
+			//  has adaptation
+			// packet[4]: adaption_field_length
+			// packet[5]: adaption field data
+			// base: start of PES body
+			char* base = &packet[5] + packet[4];
+			int len = p - base;
+			p = (char*)memmove(base + stuff_size, base, len) + len;
+			// increase the adaption field size.
+			packet[4] += stuff_size;
+			
+			return p;
+		}
+
+		// create adaption field.
+		// adaption_field_control; //2bits
+		packet[3] |= 0x20;
+		// base: start of PES body
+		char* base = &packet[4];
+		int len = p - base;
+		p = (char*)memmove(base + stuff_size, base, len) + len;
+		// adaption_field_length; //8bits
+		packet[4] = (stuff_size - 1);
+		if (stuff_size >= 2) {
+			// adaption field flags.
+			packet[5] = 0;
+			// adaption data.
+			if (stuff_size > 2) {
+				memset(&packet[6], 0xff, stuff_size - 2);
+			}
+		}
+		
+		return p;
+	}
+	static char* write_pcr(char* p, int64_t pcr)
+	{
+	    *p++ = (char) (pcr >> 25);
+	    *p++ = (char) (pcr >> 17);
+	    *p++ = (char) (pcr >> 9);
+	    *p++ = (char) (pcr >> 1);
+	    *p++ = (char) (pcr << 7 | 0x7e);
+	    *p++ = 0;
+	
+	    return p;
+	}
+	static char* write_pts(char* p, u_int8_t fb, int64_t pts)
+	{
+	    int32_t val;
+	
+	    val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
+	    *p++ = val;
+	
+	    val = (((pts >> 15) & 0x7fff) << 1) | 1;
+	    *p++ = (val >> 8);
+	    *p++ = val;
+	
+	    val = (((pts) & 0x7fff) << 1) | 1;
+	    *p++ = (val >> 8);
+	    *p++ = val;
+	
+	    return p;
+	}
+};
+
 // the mpegts header specifed the video/audio pid.
 #define TS_VIDEO_PID 256
 #define TS_AUDIO_PID 257
 
+// ts aac stream id.
+#define TS_AUDIO_AAC 0xc0
+// ts avc stream id.
+#define TS_VIDEO_AVC 0xe0
+
 SrsTSMuxer::SrsTSMuxer()
 {
 	fd = -1;
+	
+	audio_buffer = new SrsCodecBuffer();
+	video_buffer = new SrsCodecBuffer();
 }
 
 SrsTSMuxer::~SrsTSMuxer()
 {
 	close();
+	
+	audio_buffer->free();
+	video_buffer->free();
+	
+	srs_freep(audio_buffer);
+	srs_freep(video_buffer);
 }
 
 int SrsTSMuxer::open(std::string _path)
@@ -306,9 +539,7 @@ int SrsTSMuxer::open(std::string _path)
 	}
 
 	// write mpegts header
-	if (::write(fd, mpegts_header, sizeof(mpegts_header)) != sizeof(mpegts_header)) {
-		ret = ERROR_HLS_WRITE_FAILED;
-		srs_error("write ts file header %s failed. ret=%d", path.c_str(), ret);
+	if ((ret = SrsMpegtsWriter::write_header(fd)) != ERROR_SUCCESS) {
 		return ret;
 	}
 	
@@ -319,7 +550,64 @@ int SrsTSMuxer::write_audio(u_int32_t time, SrsCodec* codec, SrsCodecSample* sam
 {
 	int ret = ERROR_SUCCESS;
 	
-	static u_int8_t packet[188];
+	for (int i = 0; i < sample->nb_buffers; i++) {
+		SrsCodecBuffer* buf = &sample->buffers[i];
+		int32_t size = buf->size;
+		
+		if (!buf->bytes || size <= 0 || size > 0x1fff) {
+			ret = ERROR_HLS_AAC_FRAME_LENGTH;
+			srs_error("invalid aac frame length=%d, ret=%d", size, ret);
+			return ret;
+		}
+		
+		// AAC-ADTS
+		// 6.2 Audio Data Transport Stream, ADTS
+		// in aac-iso-13818-7.pdf, page 26.
+		// fixed 7bytes header
+		static u_int8_t adts_header[7] = {0xff, 0xf1, 0x00, 0x00, 0x00, 0x0f, 0xfc};
+		/*
+		// adts_fixed_header
+		// 2B, 16bits
+		int16_t syncword; //12bits, '1111 1111 1111'
+		int8_t ID; //1bit, '0'
+		int8_t layer; //2bits, '00'
+		int8_t protection_absent; //1bit, can be '1'
+		// 12bits
+		int8_t profile; //2bit, 7.1 Profiles, page 40
+		TSAacSampleFrequency sampling_frequency_index; //4bits, Table 35, page 46
+		int8_t private_bit; //1bit, can be '0'
+		int8_t channel_configuration; //3bits, Table 8
+		int8_t original_or_copy; //1bit, can be '0'
+		int8_t home; //1bit, can be '0'
+		
+		// adts_variable_header
+		// 28bits
+		int8_t copyright_identification_bit; //1bit, can be '0'
+		int8_t copyright_identification_start; //1bit, can be '0'
+		int16_t frame_length; //13bits
+		int16_t adts_buffer_fullness; //11bits, 7FF signals that the bitstream is a variable rate bitstream.
+		int8_t number_of_raw_data_blocks_in_frame; //2bits, 0 indicating 1 raw_data_block()
+		*/
+		// profile, 2bits
+		adts_header[2] = (codec->aac_profile << 6) & 0xc0;
+		// sampling_frequency_index 4bits
+		adts_header[2] |= (codec->aac_sample_rate << 2) & 0x3c;
+		// channel_configuration 3bits
+		adts_header[2] |= (codec->aac_channels >> 1) & 0x01;
+		adts_header[3] = (codec->aac_channels << 5) & 0xc0;
+		// frame_length 13bits
+		adts_header[3] |= (size >> 11) & 0x03;
+		adts_header[4] = (size >> 3) & 0xff;
+		adts_header[5] = (size << 5) & 0xcf;
+
+		// copy to audio buffer
+		audio_buffer->append(adts_header, sizeof(adts_header));
+		audio_buffer->append(buf->bytes, buf->size);
+	}
+	
+	audio_frame.dts = audio_frame.pts = time * 90;
+	audio_frame.pid = TS_AUDIO_PID;
+	audio_frame.sid = TS_AUDIO_AAC;
 	
 	return ret;
 }
@@ -328,7 +616,45 @@ int SrsTSMuxer::write_video(u_int32_t time, SrsCodec* codec, SrsCodecSample* sam
 {
 	int ret = ERROR_SUCCESS;
 	
-	static u_int8_t packet[188];
+	static u_int8_t aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };
+	video_buffer->append(aud_nal, sizeof(aud_nal));
+	
+	for (int i = 0; i < sample->nb_buffers; i++) {
+		SrsCodecBuffer* buf = &sample->buffers[i];
+		int32_t size = buf->size;
+		
+		if (!buf->bytes || size <= 0) {
+			ret = ERROR_HLS_AVC_SAMPLE_SIZE;
+			srs_error("invalid avc sample length=%d, ret=%d", size, ret);
+			return ret;
+		}
+		
+		// sample start prefix, '00 00 00 01' or '00 00 01'
+		u_int8_t* p = aud_nal + 1;
+		u_int8_t* end = p + 3;
+		
+		// first AnnexB prefix is long (4 bytes)
+		if (i == 0) {
+			p = aud_nal;
+		}
+		video_buffer->append(p, end - p);
+		
+		// sample data
+		video_buffer->append(buf->bytes, buf->size);
+	}
+	
+	video_frame.dts = time * 90;
+	video_frame.pts = video_frame.dts + sample->cts * 90;
+	video_frame.pid = TS_VIDEO_PID;
+	video_frame.sid = TS_VIDEO_AVC;
+	video_frame.key = sample->frame_type == SrsCodecVideoAVCFrameKeyFrame;
+	
+	if ((ret = SrsMpegtsWriter::write_frame(fd, &video_frame, video_buffer)) != ERROR_SUCCESS) {
+		return ret;
+	}
+	if ((ret = SrsMpegtsWriter::write_frame(fd, &audio_frame, audio_buffer)) != ERROR_SUCCESS) {
+		return ret;
+	}
 	
 	return ret;
 }
diff --git a/trunk/src/core/srs_core_hls.hpp b/trunk/src/core/srs_core_hls.hpp
index b065613d4..fa10ed0fe 100644
--- a/trunk/src/core/srs_core_hls.hpp
+++ b/trunk/src/core/srs_core_hls.hpp
@@ -34,6 +34,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 class SrsOnMetaDataPacket;
 class SrsCommonMessage;
 class SrsCodecSample;
+class SrsCodecBuffer;
 class SrsTSMuxer;
 class SrsCodec;
 
@@ -56,11 +57,34 @@ public:
 	virtual int on_video(SrsCommonMessage* video);
 };
 
+// @see: ngx_rtmp_mpegts_frame_t
+struct mpegts_frame
+{
+    int64_t		pts;
+    int64_t		dts;
+    int  		pid;
+    int			sid;
+    int			cc;
+    bool		key;
+    
+    mpegts_frame()
+    {
+        pts = dts = 0;
+        pid = sid = cc = 0;
+        key = false;
+    }
+};
+
 class SrsTSMuxer
 {
 private:
 	int fd;
 	std::string path;
+private:
+	mpegts_frame audio_frame;
+	SrsCodecBuffer* audio_buffer;
+	mpegts_frame video_frame;
+	SrsCodecBuffer* video_buffer;
 public:
 	SrsTSMuxer();
 	virtual ~SrsTSMuxer();