一种简单的16进制字符串转换10进制double类型的方法

#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

double hexStr2double(const unsigned char* hex, const unsigned length) {

	double ret = 0;
	char hexstr[length * 2];
	memset(hexstr, 0, sizeof(hexstr));

	for(unsigned int i = 0; i < length; i++) {
		sprintf(hexstr + i * 2, "%02x", hex[i]);
	}

	sscanf(hexstr, "%llx", (unsigned long long*)&ret);

	return ret;
}

int main() {

	double ret = 0;
	//40 9e 00  00 00 00 00 00 
	unsigned char buffer[8] = {0x40, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	ret = hexStr2double(buffer, 8);	
	printf("%f\n", ret);

        //40 89  00 00 00 00 00 00
	ret = 0;
	unsigned char buffer2[8] = {0x40, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	ret = hexStr2double(buffer2, 8);	
	printf("%f\n", ret);
}

Android Video Gallery

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView
        android:id="@+id/lv"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</LinearLayout>

list_view.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/imgView"
        android:layout_width="80dp"
        android:layout_height="80dp" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

MainActivity.java

public class MainActivity extends Activity implements OnItemClickListener {

	Cursor cursor;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		ListView listView = (ListView) findViewById(R.id.lv);
		
		String[] thumbCols = { MediaStore.Video.Thumbnails.DATA,
				MediaStore.Video.Thumbnails.VIDEO_ID };

		String[] videoCols = { MediaStore.Video.Media._ID,
				MediaStore.Video.Media.DATA, MediaStore.Video.Media.TITLE,
				MediaStore.Video.Media.MIME_TYPE };

		cursor = managedQuery(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
				videoCols, null, null, null);

		ArrayList<VideoInfo> videos = new ArrayList<VideoInfo>();

		if (cursor != null && cursor.moveToFirst()) {

			do {
				VideoInfo vf = new VideoInfo();
				int id = cursor.getInt(cursor
						.getColumnIndex(MediaStore.Video.Media._ID));
				
				//查询缩略图
				Cursor thumbCursor = managedQuery(
						MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI,
						thumbCols, MediaStore.Video.Thumbnails.VIDEO_ID + "="
								+ id, null, null);
				
				if (thumbCursor != null && thumbCursor.moveToFirst()) {

					vf.thumbPath = thumbCursor.getString(thumbCursor
							.getColumnIndex(MediaStore.Video.Thumbnails.DATA));
					Log.v("Debug thumPath:", vf.thumbPath);
				}

				vf.videoPath = cursor.getString(cursor
						.getColumnIndex(MediaStore.Video.Media.DATA));
				vf.title = cursor.getString(cursor
						.getColumnIndex(MediaStore.Video.Media.TITLE));
				vf.mimeType = cursor.getString(cursor
						.getColumnIndex(MediaStore.Video.Media.MIME_TYPE));

				videos.add(vf);

			} while (cursor.moveToNext());

			listView.setAdapter(new VideoGalleryAdapter(this, videos));
			listView.setOnItemClickListener(this);

		}
	}

	private class VideoInfo {
		String videoPath;
		String thumbPath;
		String mimeType;
		String title;
	}

	private class VideoGalleryAdapter extends BaseAdapter {

		private Context context;
		private ArrayList<VideoInfo> videos;

		public VideoGalleryAdapter(Context _context,
				ArrayList<VideoInfo> _videos) {
			context = _context;
			videos = _videos;
		}

		@Override
		public int getCount() {
			return videos.size();
		}

		@Override
		public Object getItem(int position) {
			return videos.get(position);
		}

		@Override
		public long getItemId(int position) {
			return position;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
                      //复用同一个View
                      if(convert == null) {

			LayoutInflater inflater;
			// 绑定一个layout xml文件
			inflater = (LayoutInflater) context
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			View row = inflater.inflate(R.layout.list_view, null);
			ImageView thumb = (ImageView) row.findViewById(R.id.imgView);

			if (videos.get(position).thumbPath != null) {
				thumb.setImageURI(Uri.parse(videos.get(position).thumbPath));
			}
			TextView title = (TextView) row.findViewById(R.id.tv);
			title.setText(videos.get(position).title);
                        return row;

                      } else {

                        ImageView thumb = (ImageView) convertView.findViewById(R.id.imgView);

			if (videos.get(position).thumbPath != null) {
				thumb.setImageURI(Uri.parse(videos.get(position).thumbPath));
			}
			TextView title = (TextView) convertView.findViewById(R.id.tv);
			title.setText(videos.get(position).title);
                        return convertView;
                   }	
	}

	@Override
	public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {

		if (cursor.moveToPosition(arg2)) {
			int filePathIdx = cursor
					.getColumnIndex(MediaStore.Video.Media.DATA);
			int mimeTypeIdx = cursor
					.getColumnIndex(MediaStore.Video.Media.MIME_TYPE);
			String filePath = cursor.getString(filePathIdx);
			String mimeType = cursor.getString(mimeTypeIdx);

			Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
			File file = new File(filePath);
			intent.setDataAndType(Uri.fromFile(file), mimeType);

			startActivity(intent);
		}
	}

}

ref: Pro Android Media: Developing Graphics, Music, Video and Rich Media Apps for Smartphones and Tablets

git clone https://github.com/lnmcc/VideoGallery.git

Linux C 解析 FLV 文件

FLV文件组成

FLV文件由Header和Body两部分组成。

FLV Header

域名 类型 说明
Signature UI8 Signature byte always ‘F’ (0x46)
Signature UI8 Signature byte always ‘L’ (0x4C)
Signature UI8 Signature byte always ‘V’ (0x56)
Version UI8 File version (for example, 0x01 for FLV version 1)
TypeFlagsReserved UB[5] Must be 0
TypeFlagsAudio UB[1] Audio tags are present
TypeFlagsReserved UB[1] Must be 0
TypeFlagsVideo UB[1] Video tags are present
DataOffset UI32 Offset in bytes from start of file to start of body (that is, size of header)

FLV文件都是以’F”L”V’3个字符开始。在FLV version 1 中,DataOffset一般是9,也就是说FLV的头占据9个字节。

alt none

FLV文件头解析片段:

#define FLV_HEAD_LEN 9
FlvHeader* FlvReader::readHeader() {

    unsigned char *header = new unsigned char[FLV_HEAD_LEN];
    size_t readBytes = fread(header, sizeof(unsigned char), FLV_HEAD_LEN, m_fp);

    if(readBytes == FLV_HEAD_LEN) {
        if(header[0] != 'F' || header[1] != 'L' || header[2] != 'V') {
            cerr << "Not a FLV file!!!" << endl;
            delete header;
            return NULL;
        }   

        return new FlvHeader(header, FLV_HEAD_LEN);
    } else {
        delete header;
        return NULL;
    }   
}

FLV Body

域名 类型 说明
PreviousTagSize0 UI32 Always 0
Tag1 FLVTAG First tag
PreviousTagSize1 UI32 Size of previous tag, including itsheader. For FLV version 1, this valueis 11 plus the DataSize of the previoustag.
Tag2 FLVTAG Second tag

alt none

图片来自网络

FLV的Body部分是由许多个TAG组成,每一个TAG也是有Tag Header和Tag Body组成。查看上图注意到,每一个TAG之前都有一个PreviousTagSize,一个unsigned int值,用来指示本TAG之前那个TAG的长度,唯一的例外是紧跟在FLV Header之后的那个PreviousTagSize,它的值一定是0 。

alt none

FLV Tag组成

每一个TAG也是由Tag Header和Tag Body组成。

域名 类型 说明
TagType UI8 Type of this tag. Values are:8: audio9: video18: script dataall others: reserved
DataSize UI24 Length of the data in the Data field
Timestamp UI24 Time in milliseconds at which thedata in this tag applies. This value isrelative to the first tag in the FLVfile, which always has a timestampof 0
TimestampExtended UI8 Extension of the Timestamp field toform a UI32 value. This fieldrepresents the upper 8 bits, whilethe previous Timestamp fieldrepresents the lower 24 bits of thetime in milliseconds
StreamID UI24 Always 0
Data If TagType = 8AUDIODATAIf TagType = 9VIDEODATAIf TagType = 18SCRIPTDATAOBJECT Body of the tag

Tag Header

一个TagHeader占据11个字节

alt none

Tag类型分类

script data

script data: 十六进制值为0x12(18)。这个类型的Tag通常被称为Metadata Tag,存有关于FLV视频和音频的参数信息,如duration、width、height等。通常该类型Tag会紧跟在FLV Header之后,一个FLV只能有一个Metadata Tag。 metadata Tag由两个AMF(Action Message Format)组成:

  1. 第1个AMF: 第1个字节表示AMF包类型,一般为0x02,表示后面跟的数据是字符串 第2,3个字节为UI16类型值,表示后面字符串的长度,一般为0x000A(即“onMetaData”的长度) 第4-13个字节为字符串数据,一般为“onMetaData” alt none

  2. 第2个AMF: 第1个字节表示AMF包类型,一般为0x08,表示后面的数据部分是一个数组 alt none

    第2-5个字节为UI32类型值,表示数组元素的个数,下图所示00000011(HEX) = 17(DEC),表示名称/值数组长度为17 alt none

    后面即为各数组元素的封装,数组元素为名称/值对。表示方法如下: 第1-2个字节表示元素名称的长度,假设为L,读取后面的L个字节即可得到这个名称字符串。下图所示0008(HEX)=8(DEC),表示这个名称字符串的长度为8 (实际就是“duration”的长度) alt none

    “duration”字符串 alt none

    接下来1byte是值类型,下图的00(HEX)表示Number type,占8byte,这样后面的8byte即为值(这里需要把16进制数转换成double) alt none

AMF的格式:数据类型  +  数据长度  +  数据
数据类型占 1byte
AMF数据类型有:
0 = Number type  (DOUBLE : 8byte)
1 = Boolean type (UI8 : 1byte)
2 = String type  (2byte)
3 = Object type   
4 = MovieClip type 
5 = Null type
6 = Undefined type
7 = Reference type  (UI16 : 2byte)
8 = ECMA array type 
10 = Strict array type
11 = Date type
12 = Long string type  (4byte)

alt none 上图中的0x12(18)意味着这个Tag是一个script。后面3个字节是Tag Body的大小。

读取一个MetaData的代码片段:

FlvMetaData* FlvReader::readMeta() {

	unsigned char tagHeader[TAG_HEAD_LEN];
	size_t readBytes;

	fseek(m_fp, 9 + 4, SEEK_SET);	
	//read Tag header
	readBytes = fread(tagHeader, sizeof(unsigned char), TAG_HEAD_LEN, m_fp);

	if(readBytes == TAG_HEAD_LEN) {
		//0x12: meta data 
		if(tagHeader[0] == 0x12 ) {
			 unsigned int tagBodySize = 0;
			 tagBodySize |= tagHeader[1];
			 tagBodySize = tagBodySize << 8;
			 tagBodySize |= tagHeader[2];
			 tagBodySize = tagBodySize << 8;
			 tagBodySize |= tagHeader[3];
				
			 //cerr << "MetaBody size: " << tagBodySize << endl;
		
			 unsigned char *tag = new unsigned char[tagBodySize + TAG_HEAD_LEN];
			 memcpy(tag, tagHeader, TAG_HEAD_LEN);

			 readBytes = fread(&tag[TAG_HEAD_LEN], sizeof(unsigned char), tagBodySize, m_fp);
			if(readBytes == tagBodySize) {
				return new FlvMetaData(tag, tagBodySize + TAG_HEAD_LEN);
			} else {
				delete tag;
				return NULL;
			}
		}
	}
	return NULL;
}

分析一个MetaData的代码片段:

void FlvMetaData::parseMeta() {

	unsigned int arrayLen = 0;
	unsigned int offset = TAG_HEAD_LEN + 13;

	unsigned int nameLen = 0;
	double numValue = 0;	
	bool boolValue = false;

	if(m_meta[offset++] == 0x08) {

		arrayLen |= m_meta[offset++];
		arrayLen = arrayLen << 8;
		arrayLen |= m_meta[offset++];
		arrayLen = arrayLen << 8;
		arrayLen |= m_meta[offset++];
		arrayLen = arrayLen << 8;
		arrayLen |= m_meta[offset++];
		
	} else {
		//TODO:
		cerr << "metadata format error!!!" << endl;
		return ;
	}

	for(unsigned int i = 0; i < arrayLen; i++) {
		
		numValue = 0;	
		boolValue = false;

		nameLen = 0;
		nameLen |= m_meta[offset++];
		nameLen = nameLen << 8;
		nameLen |= m_meta[offset++];
		
		char name[nameLen + 1];

		memset(name, 0, sizeof(name));
		memcpy(name, &m_meta[offset], nameLen);	
		name[nameLen + 1] = '\0';
		offset += nameLen;

		switch(m_meta[offset++]) {
		case 0x0: //Number type

			numValue = hexStr2double(&m_meta[offset], 8);
			offset += 8;
			break;

		case 0x1: //Boolean type
			if(offset++ != 0x00) {
				boolValue = true;		
			}
			break;

		case 0x2: //String type	
			nameLen = 0;
			nameLen |= m_meta[offset++];
			nameLen = nameLen << 8;
			nameLen |= m_meta[offset++];
			offset += nameLen;	
			break;

		case 0x12: //Long string type
			nameLen = 0;
			nameLen |= m_meta[offset++];
			nameLen = nameLen << 8;
			nameLen |= m_meta[offset++];
			nameLen = nameLen << 8;
			nameLen |= m_meta[offset++];
			nameLen = nameLen << 8;
			nameLen |= m_meta[offset++];
			offset += nameLen;	
			break;

		//FIXME:
		default:
			break;
		}

		if(strncmp(name, "duration", 8)	== 0) {
			m_duration = numValue;	
		} else if(strncmp(name, "width", 5)	== 0) {
			m_width = numValue;	
		} else if(strncmp(name, "height", 6) == 0) {
			m_height = numValue;	
		} else if(strncmp(name, "framerate", 9) == 0) {
			m_framerate = numValue;
		} else if(strncmp(name, "videodatarate", 13) == 0) {
			m_videodatarate = numValue;
		} else if(strncmp(name, "audiodatarate", 13) == 0) {
			m_audiodatarate = numValue;
		} else if(strncmp(name, "videocodecid", 12) == 0) {
			m_videocodecid = numValue;
		} else if(strncmp(name, "audiosamplerate", 15) == 0) {
			m_audiosamplerate = numValue;
		} else if(strncmp(name, "audiosamplesize", 15) == 0) {
			m_audiosamplesize = numValue;
		} else if(strncmp(name, "audiocodecid", 12) == 0) {
			m_audiocodecid = numValue;
		} else if(strncmp(name, "stereo", 6) == 0) {
			m_stereo = boolValue;
		}

	}
}

git clone https://github.com/lnmcc/FlvParser.git

Android使用MediaPlayer播放视频步骤

MediaPlayer状态变换图:

alt none

图片来自Google Android Doc

public class MainActivity extends Activity {

	Display display;
	SurfaceHolder surfaceHolder;
	SurfaceView surfaceView;
	MediaPlayer mediaPlayer;
	// 视频宽高
	int width = 0;
	int height = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		display = getWindowManager().getDefaultDisplay();

		String file = Environment.getExternalStorageDirectory()
				.getAbsolutePath() + "/Movies/1.m4v";

		surfaceView = (SurfaceView) findViewById(R.id.sv);
		surfaceHolder = surfaceView.getHolder();

		surfaceHolder.addCallback(new SurfaceHolder.Callback() {

			// 当SurfaceView销毁时调用
			@Override
			public void surfaceDestroyed(SurfaceHolder holder) {
				// TODO Auto-generated method stub
			}

			@Override
			public void surfaceCreated(SurfaceHolder holder) {
                                // MediaPlayer通过SurfaceHolder与SurfaceView联系在一起
				mediaPlayer.setDisplay(holder);
			}

			// 当SurfaceView大小发生变化时调用
			@Override
			public void surfaceChanged(SurfaceHolder holder, int format,
					int width, int height) {
				// TODO Auto-generated method stub
			}
		});

		mediaPlayer = new MediaPlayer();

		mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

					@Override
					public void onCompletion(MediaPlayer mp) {
						// TODO Auto-generated method stub

					}
				});

		mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {

					@Override
					public void onCompletion(MediaPlayer mp) {
						Log.v("MediaPlayer", "play completion");
					}
				});

		mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {

			@Override
			public boolean onError(MediaPlayer mp, int what, int extra) {

				if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
					Log.v("MediaPlayer", "MediaPlayer Server died");
				} else if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN) {
					Log.v("MediaPlayer", "Unknown error in MediaPlayer: "
							+ extra);
				}

				// 这里返回false表明没有处理捕捉到的错误,将交由上层来处理。如果mediaPlayer注册了OnCompletionListener,系统将会调用其onCompletion方法
				return false;
			}
		});

		mediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {

			@Override
			public void onSeekComplete(MediaPlayer mp) {
				Log.v("MediaPlayer", "MediaPlayer seek complete");
			}
		});

		mediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {

			@Override
			public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
				Log.v("MediaPlayer", "VideoSizeChanged: " + String.valueOf(width) + "x" + String.valueOf(height));
			}
		});

		mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {

			@Override
			public void onPrepared(MediaPlayer mp) {

				// 获取视频大小
				width = mp.getVideoWidth();
				height = mp.getVideoHeight();

				// 对视频宽高进行缩放
				if (width > display.getWidth() || height > display.getHeight()) {
					float heightRatio = (float) height / display.getHeight();
					float widthRatio = (float) width / display.getWidth();

					if (heightRatio > 1 || widthRatio > 1) {

						if (heightRatio > widthRatio) {
							height = (int) Math.ceil(height / heightRatio);
							width = (int) Math.ceil(width / heightRatio);
						} else {
							height = (int) Math.ceil(height / widthRatio);
							width = (int) Math.ceil(width / widthRatio);
						}
					}
				}

				// 设置SurfaceView的大小
				surfaceView.setLayoutParams(new LinearLayout.LayoutParams(
						width, height));

				//开始播放
				mp.start();
			}
		});

		try {
			mediaPlayer.setDataSource(file);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			finish();
		} catch (IllegalStateException e) {
			e.printStackTrace();
			finish();
		} catch (IOException e) {
			e.printStackTrace();
			finish();
		}

		try {
			mediaPlayer.prepareAsync();
		} catch (IllegalStateException e) {
			e.printStackTrace();
			finish();
		}

	}
}

git clone https://github.com/lnmcc/VideoPlayerUsingMediaPlayer.git

理想的设计过程模型

alt none

ref:《设计原本》