1. 程式人生 > >音訊實時傳輸和播放AMR硬編碼與硬解碼

音訊實時傳輸和播放AMR硬編碼與硬解碼

在Android中我所知道的音訊編解碼有兩種方式:

(一)使用AudioRecord採集音訊,用這種方式採集的是未經壓縮的音訊流;用AudioTrack播放實時音訊流。用這兩個類的話,如果需要對音訊進行編解碼,就需要自己移植編解碼庫了,比如可以移植ilbc,speex等開源編解碼庫。

(二)使用MediaRecorder獲取編碼後的AMR音訊,但由於MediaRecorder的特點,只能將流儲存到檔案中,但通過其他方式是可以獲取到實時音訊流的,這篇文章將介紹用LocalSocket的方法來實現;使用MediaPlayer來播放AMR音訊流,但同樣MediaPlayer也只能播放檔案流,因此我用快取的方式來播放音訊。

以上兩種方式各有利弊,使用方法(一)需移植編解碼庫,但可以播放實時音訊流;使用方法(二)直接硬編硬解碼效率高,但是需要對檔案進行操作。

PS:這篇文章只是給大家一個參考,僅供學習之用,如果真正用到專案中還有很多地方需要優化。

我強烈推薦播放音訊時候用方法(一),方法(二)雖然能夠實現功能,但是實現方式不太好。

接下來看程式碼:

編碼器:

package cn.edu.xmu.zgy.audio.encoder;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import cn.edu.xmu.zgy.config.CommonConfig;

import android.app.Activity;
import android.media.MediaRecorder;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
import android.widget.Toast;

//blog.csdn.net/zgyulongfei
//Email: 
[email protected]
public class AmrAudioEncoder { private static final String TAG = "ArmAudioEncoder"; private static AmrAudioEncoder amrAudioEncoder = null; private Activity activity; private MediaRecorder audioRecorder; private boolean isAudioRecording; private LocalServerSocket lss; private LocalSocket sender, receiver; private AmrAudioEncoder() { } public static AmrAudioEncoder getArmAudioEncoderInstance() { if (amrAudioEncoder == null) { synchronized (AmrAudioEncoder.class) { if (amrAudioEncoder == null) { amrAudioEncoder = new AmrAudioEncoder(); } } } return amrAudioEncoder; } public void initArmAudioEncoder(Activity activity) { this.activity = activity; isAudioRecording = false; } public void start() { if (activity == null) { showToastText("音訊編碼器未初始化,請先執行init方法"); return; } if (isAudioRecording) { showToastText("音訊已經開始編碼,無需再次編碼"); return; } if (!initLocalSocket()) { showToastText("本地服務開啟失敗"); releaseAll(); return; } if (!initAudioRecorder()) { showToastText("音訊編碼器初始化失敗"); releaseAll(); return; } this.isAudioRecording = true; startAudioRecording(); } private boolean initLocalSocket() { boolean ret = true; try { releaseLocalSocket(); String serverName = "armAudioServer"; final int bufSize = 1024; lss = new LocalServerSocket(serverName); receiver = new LocalSocket(); receiver.connect(new LocalSocketAddress(serverName)); receiver.setReceiveBufferSize(bufSize); receiver.setSendBufferSize(bufSize); sender = lss.accept(); sender.setReceiveBufferSize(bufSize); sender.setSendBufferSize(bufSize); } catch (IOException e) { ret = false; } return ret; } private boolean initAudioRecorder() { if (audioRecorder != null) { audioRecorder.reset(); audioRecorder.release(); } audioRecorder = new MediaRecorder(); audioRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); audioRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); final int mono = 1; audioRecorder.setAudioChannels(mono); audioRecorder.setAudioSamplingRate(8000); audioRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); audioRecorder.setOutputFile(sender.getFileDescriptor()); boolean ret = true; try { audioRecorder.prepare(); audioRecorder.start(); } catch (Exception e) { releaseMediaRecorder(); showToastText("手機不支援錄音此功能"); ret = false; } return ret; } private void startAudioRecording() { new Thread(new AudioCaptureAndSendThread()).start(); } public void stop() { if (isAudioRecording) { isAudioRecording = false; } releaseAll(); } private void releaseAll() { releaseMediaRecorder(); releaseLocalSocket(); amrAudioEncoder = null; } private void releaseMediaRecorder() { try { if (audioRecorder == null) { return; } if (isAudioRecording) { audioRecorder.stop(); isAudioRecording = false; } audioRecorder.reset(); audioRecorder.release(); audioRecorder = null; } catch (Exception err) { Log.d(TAG, err.toString()); } } private void releaseLocalSocket() { try { if (sender != null) { sender.close(); } if (receiver != null) { receiver.close(); } if (lss != null) { lss.close(); } } catch (IOException e) { e.printStackTrace(); } sender = null; receiver = null; lss = null; } private boolean isAudioRecording() { return isAudioRecording; } private void showToastText(String msg) { Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show(); } private class AudioCaptureAndSendThread implements Runnable { public void run() { try { sendAmrAudio(); } catch (Exception e) { Log.e(TAG, "sendAmrAudio() 出錯"); } } private void sendAmrAudio() throws Exception { DatagramSocket udpSocket = new DatagramSocket(); DataInputStream dataInput = new DataInputStream(receiver.getInputStream()); skipAmrHead(dataInput); final int SEND_FRAME_COUNT_ONE_TIME = 10;// 每次傳送10幀的資料,1幀大約32B // AMR格式見部落格:http://blog.csdn.net/dinggo/article/details/1966444 final int BLOCK_SIZE[] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 }; byte[] sendBuffer = new byte[1024]; while (isAudioRecording()) { int offset = 0; for (int index = 0; index < SEND_FRAME_COUNT_ONE_TIME; ++index) { if (!isAudioRecording()) { break; } dataInput.read(sendBuffer, offset, 1); int blockIndex = (int) (sendBuffer[offset] >> 3) & 0x0F; int frameLength = BLOCK_SIZE[blockIndex]; readSomeData(sendBuffer, offset + 1, frameLength, dataInput); offset += frameLength + 1; } udpSend(udpSocket, sendBuffer, offset); } udpSocket.close(); dataInput.close(); releaseAll(); } private void skipAmrHead(DataInputStream dataInput) { final byte[] AMR_HEAD = new byte[] { 0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A }; int result = -1; int state = 0; try { while (-1 != (result = dataInput.readByte())) { if (AMR_HEAD[0] == result) { state = (0 == state) ? 1 : 0; } else if (AMR_HEAD[1] == result) { state = (1 == state) ? 2 : 0; } else if (AMR_HEAD[2] == result) { state = (2 == state) ? 3 : 0; } else if (AMR_HEAD[3] == result) { state = (3 == state) ? 4 : 0; } else if (AMR_HEAD[4] == result) { state = (4 == state) ? 5 : 0; } else if (AMR_HEAD[5] == result) { state = (5 == state) ? 6 : 0; } if (6 == state) { break; } } } catch (Exception e) { Log.e(TAG, "read mdat error..."); } } private void readSomeData(byte[] buffer, int offset, int length, DataInputStream dataInput) { int numOfRead = -1; while (true) { try { numOfRead = dataInput.read(buffer, offset, length); if (numOfRead == -1) { Log.d(TAG, "amr...no data get wait for data coming....."); Thread.sleep(100); } else { offset += numOfRead; length -= numOfRead; if (length <= 0) { break; } } } catch (Exception e) { Log.e(TAG, "amr..error readSomeData"); break; } } } private void udpSend(DatagramSocket udpSocket, byte[] buffer, int sendLength) { try { InetAddress ip = InetAddress.getByName(CommonConfig.SERVER_IP_ADDRESS.trim()); int port = CommonConfig.AUDIO_SERVER_UP_PORT; byte[] sendBuffer = new byte[sendLength]; System.arraycopy(buffer, 0, sendBuffer, 0, sendLength); DatagramPacket packet = new DatagramPacket(sendBuffer, sendLength); packet.setAddress(ip); packet.setPort(port); udpSocket.send(packet); } catch (IOException e) { e.printStackTrace(); } } } }
關於編碼器:前面提到了,MediaRecorder的硬編碼的方式只能將碼流儲存到檔案中,這裡用了LocalSocket的方式將流儲存到記憶體中,然後從緩衝中讀取碼流。由於儲存的格式RAW_AMR格式的,因此需要對讀取到的資料進行解析,從而獲得真正的音訊流。想了解AMR音訊碼流格式的,可以檢視程式碼中附上的網頁連結。由於壓縮過的碼流很小,因此我在實現的時候,組合了int SEND_FRAME_COUNT_ONE_TIME = 10幀的碼流後才往外發送,這樣的方式造成的延遲會加重,大家可以根據自己的需要進行修改。造成延遲的另一因素是LocalSocket緩衝的大小,在這裡我設定的大小是final int bufSize = 1024;程式碼寫的很清楚詳細,有疑問的可以提出。

播放器:

package cn.edu.xmu.zgy.audio.player;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;

import cn.edu.xmu.zgy.config.CommonConfig;

import android.app.Activity;
import android.media.MediaPlayer;
import android.os.Handler;
import android.util.Log;

//blog.csdn.net/zgyulongfei
//Email: [email protected]

public class AmrAudioPlayer {
	private static final String TAG = "AmrAudioPlayer";

	private static AmrAudioPlayer playerInstance = null;

	private long alreadyReadByteCount = 0;

	private MediaPlayer audioPlayer;
	private Handler handler = new Handler();

	private final String cacheFileName = "audioCacheFile";
	private File cacheFile;
	private int cacheFileCount = 0;

	// 用來記錄是否已經從cacheFile中複製資料到另一個cache中
	private boolean hasMovedTheCacheFlag;

	private boolean isPlaying;
	private Activity activity;

	private boolean isChaingCacheToAnother;

	private AmrAudioPlayer() {
	}

	public static AmrAudioPlayer getAmrAudioPlayerInstance() {
		if (playerInstance == null) {
			synchronized (AmrAudioPlayer.class) {
				if (playerInstance == null) {
					playerInstance = new AmrAudioPlayer();
				}
			}
		}
		return playerInstance;
	}

	public void initAmrAudioPlayer(Activity activity) {
		this.activity = activity;
		deleteExistCacheFile();
		initCacheFile();
	}

	private void deleteExistCacheFile() {
		File cacheDir = activity.getCacheDir();
		File[] needDeleteCacheFiles = cacheDir.listFiles();
		for (int index = 0; index < needDeleteCacheFiles.length; ++index) {
			File cache = needDeleteCacheFiles[index];
			if (cache.isFile()) {
				if (cache.getName().contains(cacheFileName.trim())) {
					Log.e(TAG, "delete cache file: " + cache.getName());
					cache.delete();
				}
			}
		}
		needDeleteCacheFiles = null;
	}

	private void initCacheFile() {
		cacheFile = null;
		cacheFile = new File(activity.getCacheDir(), cacheFileName);
	}

	public void start() {
		isPlaying = true;
		isChaingCacheToAnother = false;
		setHasMovedTheCacheToAnotherCache(false);
		new Thread(new NetAudioPlayerThread()).start();
	}

	public void stop() {
		isPlaying = false;
		isChaingCacheToAnother = false;
		setHasMovedTheCacheToAnotherCache(false);
		releaseAudioPlayer();
		deleteExistCacheFile();
		cacheFile = null;
		handler = null;
	}

	private void releaseAudioPlayer() {
		playerInstance = null;
		if (audioPlayer != null) {
			try {
				if (audioPlayer.isPlaying()) {
					audioPlayer.pause();
				}
				audioPlayer.release();
				audioPlayer = null;
			} catch (Exception e) {
			}
		}
	}

	private boolean hasMovedTheCacheToAnotherCache() {
		return hasMovedTheCacheFlag;
	}

	private void setHasMovedTheCacheToAnotherCache(boolean result) {
		hasMovedTheCacheFlag = result;
	}

	private class NetAudioPlayerThread implements Runnable {
		// 從接受資料開始計算,當快取大於INIT_BUFFER_SIZE時候開始播放
		private final int INIT_AUDIO_BUFFER = 2 * 1024;
		// 剩1秒的時候播放新的快取的音樂
		private final int CHANGE_CACHE_TIME = 1000;

		public void run() {
			try {
				Socket socket = createSocketConnectToServer();
				receiveNetAudioThenPlay(socket);
			} catch (Exception e) {
				Log.e(TAG, e.getMessage() + "從服務端接受音訊失敗。。。");
			}
		}

		private Socket createSocketConnectToServer() throws Exception {
			String hostName = CommonConfig.SERVER_IP_ADDRESS;
			InetAddress ipAddress = InetAddress.getByName(hostName);
			int port = CommonConfig.AUDIO_SERVER_DOWN_PORT;
			Socket socket = new Socket(ipAddress, port);
			return socket;
		}

		private void receiveNetAudioThenPlay(Socket socket) throws Exception {
			InputStream inputStream = socket.getInputStream();
			FileOutputStream outputStream = new FileOutputStream(cacheFile);

			final int BUFFER_SIZE = 100 * 1024;// 100kb buffer size
			byte[] buffer = new byte[BUFFER_SIZE];

			// 收集了10*350b了之後才開始更換快取
			int testTime = 10;
			try {
				alreadyReadByteCount = 0;
				while (isPlaying) {
					int numOfRead = inputStream.read(buffer);
					if (numOfRead <= 0) {
						break;
					}
					alreadyReadByteCount += numOfRead;
					outputStream.write(buffer, 0, numOfRead);
					outputStream.flush();
					try {
						if (testTime++ >= 10) {
							Log.e(TAG, "cacheFile=" + cacheFile.length());
							testWhetherToChangeCache();
							testTime = 0;
						}
					} catch (Exception e) {
						// TODO: handle exception
					}

					// 如果複製了接收網路流的cache,則執行此操作
					if (hasMovedTheCacheToAnotherCache() && !isChaingCacheToAnother) {
						if (outputStream != null) {
							outputStream.close();
							outputStream = null;
						}
						// 將接收網路流的cache刪除,然後重0開始儲存
						// initCacheFile();
						outputStream = new FileOutputStream(cacheFile);
						setHasMovedTheCacheToAnotherCache(false);
						alreadyReadByteCount = 0;
					}

				}
			} catch (Exception e) {
				errorOperator();
				e.printStackTrace();
				Log.e(TAG, "socket disconnect...:" + e.getMessage());
				throw new Exception("socket disconnect....");
			} finally {
				buffer = null;
				if (socket != null) {
					socket.close();
				}
				if (inputStream != null) {
					inputStream.close();
					inputStream = null;
				}
				if (outputStream != null) {
					outputStream.close();
					outputStream = null;
				}
				stop();
			}
		}

		private void testWhetherToChangeCache() throws Exception {
			if (audioPlayer == null) {
				firstTimeStartPlayer();
			} else {
				changeAnotherCacheWhenEndOfCurrentCache();
			}
		}

		private void firstTimeStartPlayer() throws Exception {
			// 當快取已經大於INIT_AUDIO_BUFFER則開始播放
			if (alreadyReadByteCount >= INIT_AUDIO_BUFFER) {
				Runnable r = new Runnable() {
					public void run() {
						try {
							File firstCacheFile = createFirstCacheFile();
							// 設定已經從cache中複製資料,然後會刪除這個cache
							setHasMovedTheCacheToAnotherCache(true);
							audioPlayer = createAudioPlayer(firstCacheFile);
							audioPlayer.start();
						} catch (Exception e) {
							Log.e(TAG, e.getMessage() + " :in firstTimeStartPlayer() fun");
						} finally {
						}
					}
				};
				handler.post(r);
			}
		}

		private File createFirstCacheFile() throws Exception {
			String firstCacheFileName = cacheFileName + (cacheFileCount++);
			File firstCacheFile = new File(activity.getCacheDir(), firstCacheFileName);
			// 為什麼不直接播放cacheFile,而要複製cacheFile到一個新的cache,然後播放此新的cache?
			// 是為了防止潛在的讀/寫錯誤,可能在寫入cacheFile的時候,
			// MediaPlayer正試圖讀資料, 這樣可以防止死鎖的發生。
			moveFile(cacheFile, firstCacheFile);
			return firstCacheFile;

		}

		private void moveFile(File oldFile, File newFile) throws IOException {
			if (!oldFile.exists()) {
				throw new IOException("oldFile is not exists. in moveFile() fun");
			}
			if (oldFile.length() <= 0) {
				throw new IOException("oldFile size = 0. in moveFile() fun");
			}
			BufferedInputStream reader = new BufferedInputStream(new FileInputStream(oldFile));
			BufferedOutputStream writer = new BufferedOutputStream(new FileOutputStream(newFile,
					false));

			final byte[] AMR_HEAD = new byte[] { 0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A };
			writer.write(AMR_HEAD, 0, AMR_HEAD.length);
			writer.flush();

			try {
				byte[] buffer = new byte[1024];
				int numOfRead = 0;
				Log.d(TAG, "POS...newFile.length=" + newFile.length() + "  old=" + oldFile.length());
				while ((numOfRead = reader.read(buffer, 0, buffer.length)) != -1) {
					writer.write(buffer, 0, numOfRead);
					writer.flush();
				}
				Log.d(TAG, "POS..AFTER...newFile.length=" + newFile.length());
			} catch (IOException e) {
				Log.e(TAG, "moveFile error.. in moveFile() fun." + e.getMessage());
				throw new IOException("moveFile error.. in moveFile() fun.");
			} finally {
				if (reader != null) {
					reader.close();
					reader = null;
				}
				if (writer != null) {
					writer.close();
					writer = null;
				}
			}
		}

		private MediaPlayer createAudioPlayer(File audioFile) throws IOException {
			MediaPlayer mPlayer = new MediaPlayer();

			// It appears that for security/permission reasons, it is better to
			// pass
			// a FileDescriptor rather than a direct path to the File.
			// Also I have seen errors such as "PVMFErrNotSupported" and
			// "Prepare failed.: status=0x1" if a file path String is passed to
			// setDataSource(). So unless otherwise noted, we use a
			// FileDescriptor here.
			FileInputStream fis = new FileInputStream(audioFile);
			mPlayer.reset();
			mPlayer.setDataSource(fis.getFD());
			mPlayer.prepare();
			return mPlayer;
		}

		private void changeAnotherCacheWhenEndOfCurrentCache() throws IOException {
			// 檢查當前cache剩餘時間
			long theRestTime = audioPlayer.getDuration() - audioPlayer.getCurrentPosition();
			Log.e(TAG, "theRestTime=" + theRestTime + "  isChaingCacheToAnother="
					+ isChaingCacheToAnother);
			if (!isChaingCacheToAnother && theRestTime <= CHANGE_CACHE_TIME) {
				isChaingCacheToAnother = true;

				Runnable r = new Runnable() {
					public void run() {
						try {
							File newCacheFile = createNewCache();
							// 設定已經從cache中複製資料,然後會刪除這個cache
							setHasMovedTheCacheToAnotherCache(true);
							transferNewCacheToAudioPlayer(newCacheFile);
						} catch (Exception e) {
							Log.e(TAG, e.getMessage()
									+ ":changeAnotherCacheWhenEndOfCurrentCache() fun");
						} finally {
							deleteOldCache();
							isChaingCacheToAnother = false;
						}
					}
				};
				handler.post(r);
			}
		}

		private File createNewCache() throws Exception {
			// 將儲存網路資料的cache複製到newCache中進行播放
			String newCacheFileName = cacheFileName + (cacheFileCount++);
			File newCacheFile = new File(activity.getCacheDir(), newCacheFileName);
			Log.e(TAG, "before moveFile............the size=" + cacheFile.length());
			moveFile(cacheFile, newCacheFile);
			return newCacheFile;
		}

		private void transferNewCacheToAudioPlayer(File newCacheFile) throws Exception {
			MediaPlayer oldPlayer = audioPlayer;

			try {
				audioPlayer = createAudioPlayer(newCacheFile);
				audioPlayer.start();
			} catch (Exception e) {
				Log.e(TAG, "filename=" + newCacheFile.getName() + " size=" + newCacheFile.length());
				Log.e(TAG, e.getMessage() + " " + e.getCause() + " error start..in transfanNer..");
			}
			try {
				oldPlayer.pause();
				oldPlayer.reset();
				oldPlayer.release();
			} catch (Exception e) {
				Log.e(TAG, "ERROR release oldPlayer.");
			} finally {
				oldPlayer = null;
			}
		}

		private void deleteOldCache() {
			int oldCacheFileCount = cacheFileCount - 1;
			String oldCacheFileName = cacheFileName + oldCacheFileCount;
			File oldCacheFile = new File(activity.getCacheDir(), oldCacheFileName);
			if (oldCacheFile.exists()) {
				oldCacheFile.delete();
			}
		}

		private void errorOperator() {
		}
	}

}
關於播放器:由於MediaPlayer的限制,我用了cache的方式來實現音訊的實時播放。即把獲取到的音訊流首先儲存到檔案中,然後當儲存到一定大小的時候就播放之,類似於QQ播放器那樣有快取的,只不過我這裡的處理得挺粗糙。


相關推薦

音訊實時傳輸播放AMR編碼解碼

在Android中我所知道的音訊編解碼有兩種方式: (一)使用AudioRecord採集音訊,用這種方式採集的是未經壓縮的音訊流;用AudioTrack播放實時音訊流。用這兩個類的話,如果需要對音訊進行編解碼,就需要自己移植編解碼庫了,比如可以移植ilbc,speex等開源

Android音訊實時傳輸播放(三):AMR編碼解碼

轉載請註明出處! 在Android中我所知道的音訊編解碼有兩種方式: (一)使用AudioRecord採集音訊,用這種方式採集的是未經壓縮的音訊流;用AudioTrack播放實時音訊流。用這兩個類的話,如果需要對音訊進行編解碼,就需要自己移植編解碼庫了,比如可以移植il

Android音訊實時傳輸播放(一)

服務端共開放兩個埠,一個udp上行埠用來接收amr音訊流,另一個tcp下行埠用來發送amr音訊流。 我這裡寫的服務端實現了組播的功能,即一個人在錄音,可以同時讓很多人同時聽到。 簡而言之,服務端做的唯一一件事情就是轉發音訊流,囧rz。。。 在這裡,我只貼出一部分程式碼,後

Android音訊實時傳輸播放(二):服務端

我偷懶就用java寫了個簡單的伺服器,大家明白原理就好。 服務端共開放兩個埠,一個udp上行埠用來接收amr音訊流,另一個tcp下行埠用來發送amr音訊流。 我這裡寫的服務端實現了組播的功能,即一個人在錄音,可以同時讓很多人同時聽到。 簡而言之,服務端做的唯一一件

iOS 實時錄音播放

需求:最近公司需要做一個樓宇對講的功能:門口機(連線WIFI)撥號對室內機(對應的WIFI)的裝置進行呼叫,室內機收到呼叫之後將對收到的資料進行UDP廣播的轉發,手機(連線對應的WIFI)收到視訊流之後,實時的展示視訊資料(手機可以接聽,結束通話,手機接聽之後,室內機不展

HTML5+規範:audio(音訊的錄製播放功能)

   Audio模組用於提供音訊的錄製和播放功能,可呼叫系統的麥克風裝置進行錄音操作,也可呼叫系統的揚聲器裝置播放音訊檔案。通過plus.audio獲取音訊管理物件。 1、常量   ROUTE_SPE

編碼編碼

硬編碼:就是將資料直接寫入到程式碼中進行編譯開發,比如在沒有mybatits前,將sql語句寫入到jdbc程式碼裡,在比如純jsp開發的過程中,將前端的html程式碼與java程式碼耦合,這都是應編碼,如果要發生更改的問題,就需要更改原始碼,如果是C/S開發,就直接一位這,客戶端的軟體需

FFmpeg,H.264,Directshow,opencv及視訊編碼封裝格式

http://www.voidcn.com/blog/yhhyhhyhhyhh/article/p-5769736.html     最近做專案,參考網上的資料,梳理了一下視訊採集,編碼,傳輸,顯示相關知識及常用的視訊處理框架和開源庫(本文有誤的地方請見諒,好多概念

charQChar(Unicode的編碼內存裏的值還不是一回事)

漢字 內部 內置 tail 依然 一個 解析 返回 printf char類型是c/c++中內置的類型,描述了1個字節的內存信息的解析。比如: char gemfield=’g’;那麽在由gemfield標記的這塊內存的大小就是1個字節,信息就是

Linux 件管理

自動修復 entos ali 如果 訪問 xxx 字符 必須 由於 硬件以文件系統(Filesystem)角度來看 文件系統:一個可被掛載的數據稱為文件系統,每個操作系統可以使用的文件系統並不一樣,windows98是FAT或者FAT16文件系統,而windows200

客戶端編碼伺服器解碼全過程

做java的web開發有段日子了,有個問題老是困擾著我,就是亂碼問題,基本上是網上查詢解決方案(網上資料真的很多),都是一大堆的介紹如何解決此類的亂碼問題,但是沒幾個把問題的來龍去脈說清楚的,有時候看了些文章後,以為自己懂了,但是在開發中亂碼問題又像鬼魂一樣出來嚇人,真是頭大

C# NAudio錄音播放音訊檔案-實時繪製音訊波形圖(從音訊流資料獲取,而非裝置獲取)

  NAudio的錄音和播放錄音都有對應的類,我在使用Wav格式進行錄音和播放錄音時使用的類時WaveIn和WaveOut,這兩個類是對功能的回撥和一些事件觸發。   在WaveIn和WaveOut之外還有對音訊流讀寫使用的WaveFileWriter和WaveFileReader類,具體細節可檢視其原始碼進

Fortify漏洞之Dynamic Code Evaluation: Code Injection(動態腳本註入) Password Management: Hardcoded Password(密碼編碼

mys info 用戶輸入 strong 獲取 center 連接數 密碼 new   繼續對Fortify的漏洞進行總結,本篇主要針對 Dynamic Code Evaluation: Code Injection(動態腳本註入) 和 Password Manageme

Android藍芽socket實現視訊實時傳輸,以及圖片文字傳輸

目標 兩臺手機裝置之間能夠正常進行藍芽配對(藍芽模組兒和硬體掛鉤,所以需要兩臺真機) socket實現藍芽文字傳輸 實現圖片傳輸 實現實時視訊傳輸 程式碼下載:https://download.csdn.net/download/m0_37781149/10434336

PortAudio採集播放音訊,實現一個雙路混音器

混音,顧名思義,就是把多個音源混合的過程,是一個很常見的應用。這兩天我也做了一個雙路混音器,當然,我沒有做多麼專業的音訊訊號處理,只是一個簡單的混音,調節各路音量,並實現了一些音效處理。主要功能有:採集硬體裝置,讀取wav檔案,播放,混音,音量調節,音訊節奏、音調的調節,wa

藍芽晶片支援音訊傳輸播放U盤TF卡,還能傳輸資料透傳串列埠控制

一、前言             選型是很關鍵的一環,因為選型的結果,就會影響到整個產品的價效比,以及開發週期等等一些列的問題,所以這裡我們做了一些系統化的分類,幫助工程師挑選到最合適的方案         藍芽晶片技術的成熟,以及應用場景的越來越豐富,所以催生了多種多樣的

使用servlet過濾器播放amr音訊

前話         怎樣播放amr音訊?這個問題讓我好煩惱,在網上找了一些資料,quicktime外掛雖然可以播放amr格式的音訊,但是不滿足專案的要求,html5也不能播放amr格式的音訊。後來想到將amr音訊轉成其他HTML5支援的格式不久行了,後來在網上找到JAVE

alsa音訊採集播放 (麥克風)

Alsa音訊採集 #include <stdio.h> #include <malloc.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include 

Jenkins高階篇之Pipeline實踐篇-8-SeleniumJenkins持續整合-新增事後刪除報告功能解決報告名稱編碼

這篇,我們第一件事情來實現把html報告publish完成之後就刪除報告檔案。這個是很有必要的操作,雖然我們前面寫死了報告名稱為index.html,你跑多次測試,都會在test-output資料夾下覆蓋原來的html報告檔案。但是,就像我們最早的時候,報告名稱是特定文字加時間戳命名,那麼如果不

iOS之視訊編碼編碼解碼、軟解碼

軟編碼:使用CPU進行編碼。編碼框架ffmpeg+x264。 硬編碼:不使用CPU進行編碼,使用顯示卡GPU,專用的DSP、FPGA、ASIC晶片等硬體進行編碼。編碼框架Video ToolBox和AudioToolbox。