Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

nprog

Streaming de audio

Recommended Posts

Boa tarde pessoal.

 

 

Estou desenvolvendo um software para transmissão de uma web radio em C#, estou com algumas dificuldades...

Alguém conhece algum programa tipo SamCast/SimpleCast open source?

 

Consegui capturar o audio da maquina, mas não estou conseguindo subir para o servidor.

 

Esse parte de envio de bytes enquanto esta sendo gravado o audio alguem sabe como funciona?

 

 

Obg

Compartilhar este post


Link para o post
Compartilhar em outros sites

Olá amigo, este fórum é apenas para ASP Classic, por favor poste no fórum de .Net. Mas aqui vai um exemplo:

 

Use a nova classe PushStreamContent contida no namespace System.Net.Http. Essa classe permite que possamos enviar pacotes por demanda para o cliente. A referência que tive veio do blog Andru's WebLog. Em nosso caso leremos o arquivo do disco do servidor e o enviaremos pouco a pouco para o cliente que visualizará ou escutará a mídia por meio de controles HTML5 de áudio e vídeo.

Implementação
//A classe recebe como construtor uma Action
public PushStreamContent(Action<stream httpcontent="" transportcontext=""> onStreamAvailable);
</stream>

 

Criei uma classe de ajuda que lê a mídia do disco do servidor e assincronamente a salva em um buffer e libera para o cliente de acordo com a demanda. Usei as novas técnicas de programação contidas no C# 5.0 async/await para realizar a tarefa.

 

public class MediaStream
{
private readonly string _filename;
public MediaStream(string filename)
{
this._filename = filename;
}
public async void WriteToStream(Stream outputStream, HttpContent content, TransportContext context)
{
try
{
var buffer = new byte[65536];
using (var media = File.Open(_filename, FileMode.Open, FileAccess.Read))
{
var length = (int)media.Length;
var bytesRead = 1;
while (length > 0 && bytesRead > 0)
{
bytesRead = media.Read(buffer, 0, Math.Min(length, buffer.Length));
await outputStream.WriteAsync(buffer, 0, bytesRead);
length -= bytesRead;
}
}
}
catch (HttpException ex)
{
return;
}
finally
{
outputStream.Close();
}
}
}

 

Note que a assinatura do método WriteToStream corresponde a assinatura do métodos PushStreamContent. Logo depois criei duas classes, uma para Vídeo e outra para Áudio ambas filhas da classe MediaStream. Fiz dessa maneira para promover uma certa extensibilidade caso seja necessário fazer algo mais complexo mais tarde.

 

public class VideoStream : MediaStream
{
public VideoStream(string filename) : base(filename)
{
}
}
public class AudioStream : MediaStream
{
public AudioStream(string filename) : base(filename)
{
}
}

 

Agora vamos para a classe Controller para áudio e vídeo, que ficaram bastante simples após a separação nas classes anteriores.

public class AudiosController : ApiController
{
// GET api/music/5
public HttpResponseMessage Get(string filename, string ext)
{
filename = @"\Content\Audio\" + filename + "." + ext;
var audio = new AudioStream(filename);
var response = Request.CreateResponse();
response.Content = new PushStreamContent(audio.WriteToStream, new MediaTypeHeaderValue("audio/" + ext));
return response;
}
}
public class VideosController : ApiController
{
// GET api/music/5
public HttpResponseMessage Get(string filename, string ext)
{
filename = @"\Content\Audio\" + filename + "." + ext;
var video = new VideoStream(filename);
var response = Request.CreateResponse();
response.Content = new PushStreamContent(video.WriteToStream, new MediaTypeHeaderValue("video/" + ext));
return response;
}
}

 

O nome do arquivo deve conter o caminho completo do mesmo em seu servidor, logo quando for fazer o seu teste lembre-se disso. Agora para que o cliente possa passar um endereço e consumir os nossos serviços é necessário que este registre uma rota com a padrão correspondendo, neste exemplo estou usando api/extensão/nomeDoArquivo:

config.Routes.MapHttpRoute(
name: "DefaultVideo",
routeTemplate: "api/{controller}/{ext}/{filename}"
);


Consumindo os serviços
Basta criar uma página e adicionar nossos controles HTML5:


<h3>
Música</h3>
<audio autoplay="autoplay" controls="controls">
<source src="/api/audios/mp3/AsRosasNaoFalam" type="audio/mpeg">
</audio>
<h3>
Video</h3>
<video controls="controls" height="320" width="480">
<source src="/api/videos/mp4/OMundoUmMoinho" type="video/mp4">
</video>

 

Agora você pode abrir alguma ferramenta como firebug e ver que o conteúdo está sendo transmitido por streaming e não sendo carregado como um todo. Uma observação é que você deve usar o Chrome para fazer seus testes e brincar com este exemplo.

 

Download

invis.gif

Compartilhar este post


Link para o post
Compartilhar em outros sites

Este é um programa desktop, e não web...

 

A minha dúvida não é ler o arquivo do servidor e rodar no navegar, e sim como enviar para o servidor os bytes do audio.

 

Consegui pegar o audio da maquina, agora não estou conseguindo enviar esses bytes para o servidor...

 

Poderia usar a classe StreamWriter para conectar ao servidor (com senha + usuario) para enviar os Bytes?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Você consegue fazer isso também com a API AUDIO do HTML5 e dá uma olhada neste artigo.

 

Uma stream pode ser definida como uma abstração entre o programador e o dispositivo que realmente esta sendo acessado, que pode ser arquivo, memória, rede, impressora e etc. Por intermédio das streams é possível transportar dados como voz, vídeo, imagem, entre outros. As streams suportam leituras e escritas síncronas e assíncronas.

E/S Síncrona

O .Net Framework provê classes para trabalhar com streams, sendo as principais: FileStream, MemoryStream, NetworkStream - derivadas de Stream, e BufferedStream que pode ser utilizada em conjunção com qualquer outra classe Stream. O primeiro passo para obter e gravar dados em arquivos, é invocar os métodos estáticos OpenRead() e OpenWrite(), da classe File, que retornam um Stream. Essas streams servirão como "canais" por onde passarão os dados conforme o exemplo abaixo.

Stream streamEntrada = File.OpenRead(@"c:\ArquivoOriginal.cs"); Stream streamSaida = File.OpenWrite(@"c:\ArquivoCopia.cs"); 

No exemplo acima foi criado um Stream de Entrada a partir do arquivo "ArquivoOriginal.cs" e um Stream de Saída a partir do arquivo "ArquivoCopia.cs".

Depois de aberto o arquivo para leitura, é iniciada a transferência dos dados através do método Read da classe Stream. A otimização dessa transferência ocorre criando-se um buffer (repositório temporário de dados) para que, uma vez lidos, os dados de um Backstore (Fonte de Dados) possam ser armazenados nesse mesmo buffer e posteriormente manipulados. O exemplo abaixo mostra a leitura do arquivo (c:\ArquivoOriginal.cs) e a escrita no arquivo (c:\ArquivoCopia.cs) através dos métodos Read e Write dos objetos streamEntrada e streamSaída :

while(bytesLidos = streamEntrada.Read(buffer,0,tamanhoBuffer)>0) { streamSaida.Write (buffer,0,bytesLidos); } 

Os parâmetros que o método Write espera são: buffer, que nada mais é do que um array de bytes que irá armazenar os dados; a posição de início de leitura; e o número de bytes a escrever que no caso do exemplo acima sempre será o mesmo número de bytes que foi lido.

streams_1.jpg

Exemplo streams síncrono :

namespace Exemplo { using System; using System.IO; class ESarquivoSincrono { const int tamanhoBuffer = 1024; public static void Main( ) { EsarquivoSincrono esArquivoSincrono = new EsarquivoSincrono ( ); esArquivoSincrono.Executa( ); } private void Executa( ) { // O arquivo que será lido Stream streamEntrada = File.OpenRead(@"c:\ArquivoOriginal.cs"); // O arquivo que será escrito Stream streamSaida = File.OpenWrite(@"c:\ArquivoCopia.cs"); // O buffer que irá armazenar o bytes byte[] buffer = new Byte[tamanhoBuffer]; int bytesLidos; //escreve no arquivo enquanto houver bytes no buffer a serem escritos while(bytesLidos = streamEntrada.Read(buffer,0,buffer.Lenght)>0) { streamSaida.Write (buffer,0,bytesLidos); } //Fecha as streams streamSaida.Close(); streamEntrada.Close( ); } } } 

Acima foi mostrado um exemplo de chamada síncrona, que não é o melhor método de trabalhar com streams, quando levado em consideração fatores importantes como performance, pois ao fazer uma chamada síncrona, enquanto seu programa lê ou escreve, todas as outras atividades são paradas. Pode-se levar muito tempo para obter dados do backstore que pode ser ou um disco muito devagar ou uma rede lenta. Desse modo a melhor alternativa é trabalhar com E/S Assíncrona.

E/S Assíncrona

E/S Assíncrona permite começar uma tarefa de E/S e exercer outras atividades. Isso ocorre através dos métodos BeginRead() e BeginWrite() da classe Stream. O fato de ser dedicado um Thread a cada tarefa de E/S possibilita a execução de tarefas concorrentes. É de praxe, ao iniciarmos uma tarefa de E/S Assíncrona, "agendarmos" um procedimento a ser realizado quando a mesma for concluída. Isso é feito passando-se um método de callback através de um delegate, onde será implementada uma outra atividade, que pode ser o processamento dos dados lidos ou escritos, a notificação que determinada tarefa foi concluída, entre outras.

Leitura Assíncrona :

streamEntrada.BeginRead( buffer, //Local onde serão armazenados os dados 0, // offset buffer.Length, // Tamanho do Buffer myCallBack, // Delegate "apontando" para o método de callback null); // Objeto contendo informações sobre estado 

Escrita Assíncrona :

streamSaida.BeginWrite( buffer, //Local onde serão armazenados os dados 0, // offset buffer.Length, // Tamanho do Buffer myCallBack, // Delegate "apontando" para o método de callback null); // Objeto contendo informações sobre estado do objeto 

streams_2.jpg

O exemplo abaixo ilustra uma pequena aplicação para leitura e escrita de dados no modo assíncrono. Inicialmente são declaradas as Streams de entrada e saída, os delegates utilizados para "apontarem" os métodos de callback para as tarefas de leitura e escrita, e um array de bytes que servirá de buffer.

namespace Exemplo { using System; using System.IO; using System.Threading; using System.Text; public class ESarquivoAssincrono { private Stream streamEntrada; private Stream streamSaida; // Declara os delegates private AsyncCallback callBackEscrita; private AsyncCallback callBackLeitura; // Buffer que irá armazenar os dados private byte[] buffer; // Define o tamanho do buffer const int tamanhoBuffer = 256; 

O construtor da classe irá instanciar: as streams de entrada e saída a partir dos métodos estáticos OpenRead e OpenWrite da classe File; o buffer; e os delegates, atribuindo a cada um dos delegates um método que irá efetuar o processamento posterior às respectivas tarefas.

 // Construtor ESarquivoAssincrono( ) { // Abre a stream de entrada streamEntrada = File.OpenRead(@"c:\ArquivoOriginal.cs"); // Abre a stream de saída streamSaída = File.OpenWrite(@"c:\ArquivoCopia.cs"); // Instancia o buffer buffer = new byte[tamanhoBuffer]; // "Aponta" o Delegate de CallBack para um //determinado método que irá processar informações após a leitura ser concluída callBackLeitura = new AsyncCallback(this.QuandoLeituraForConcluida); callBackEscrita = new AsyncCallback(this.QuandoEscritaForConcluida); } public static void Main( ) { ESarquivoAssincrono esArquivoAssincrono =new ESarquivoAssincrono ( ); // invoca o método que irá começar o processo de leitura e escrita esArquivoAssincrono. Executa( ); } 

Logo após é iniciada a leitura dos dados, invocando-se o método BeginRead(), que tem como parâmetros de entrada: o buffer, onde serão armazenados os dados lidos; o offset; o tamanho do buffer; o delegate, "apontando" para o método de callback; e um objeto, que serve para indicar o estado.Depois é efetuada uma tarefa qualquer para demonstrar o trabalho assíncrono.

 void Executa( ) { streamEntrada.BeginRead( buffer, // Onde os dados serão armazenados 0, // offset buffer.Length, // Tamanho do buffer callBackLeitura, //Delegate de callback null); // Objeto de estado Console.WriteLine("A leitura assíncrona foi iniciada\n"); Console.WriteLine("Tarefa concorrente a leitura :\n"); for (long i = 0; i < 2000; i++) { Console.WriteLine("*"); } } } } 

O próximo passo é criar o método que irá processar as informações quando a leitura for concluída. Esse método verifica se os dados foram lidos por intermédio do método EndRead() que retorna o número de bytes lidos. Caso tenham sido , os mesmos são escritos por um outro método assíncrono BeginWrite(), seguido de uma tarefa qualquer concorrente para demonstrar o trabalho assíncrono.

// Metodo que irá processar as informações quando a leitura for concluída public void QuandoLeituraForConcluida (IAsyncResult result) { //Se houver bytes lidos escreve eles em outro arquivo if (bytesLidos = streamEntrada.EndRead(result) > 0) { streamSaida.BeginWrite(buffer,0,buffer.Length, callBackEscrita, null); Console.WriteLine("A escrita assíncrona foi iniciada\n"); Console.WriteLine("Tarefa concorrente a escrita :\n"); for (long i = 0; i < 2000; i++) { Console.WriteLine("/"); } } } 

Por fim é criado o método que irá processar as informações quando a escrita for concluída. Nesse método invoca-se novamente BeginRead(), criando um ciclo, que quando a leitura é concluída, inicia a escrita, que logo terminada inicia novamente a leitura. Esse processo, que se repete até que não haja mais bytes a serem lidos, é feito através dos métodos de callback, invocados através do delegate, que são passados nos métodos de leitura e escrita.

 // Metodo que irá processar as informações quando a escrita for concluída void QuandoEscritaForConcluida (IAsyncResult result) { streamSaída.EndWrite(result); streamEntrada.BeginRead( buffer, // Onde os dados serão armazenados 0, // offset buffer.Length, // Tamanho do buffer callBackLeitura, //Delegate de callback null); // Objeto de estado Console.WriteLine("A leitura assíncrona foi iniciada\n"); Console.WriteLine("Tarefa concorrente a leitura :\n"); for (long i = 0; i 

E/S Rede

Escrever para um objeto remoto não é diferente do que escrever para um objeto local, basicamente a maior diferença é o uso de sockets. Os sockets são muito úteis para aplicações cliente-servidor, ponto-a-ponto e quando há necessidade de fazer chamadas a procedimentos remotos. Imagine um socket como uma tomada, um ponto final, para uma determinada comunicação entre processos através de uma rede. O primeiro passo para criar uma aplicação deste tipo é instanciar um socket dando a ele a tarefa de "escutar" em uma determinada porta. O socket irá esperar pacientemente por uma chamada do cliente onde irá interagir com o mesmo. No caso de haver mais de uma chamada por vez, é criada uma fila para que os clientes sejam colocados em espera. É notável, já neste momento, a deficiência deste procedimento, pois se formos tratar múltiplas conexões, este processo de enfileiramento irá causar um grande gargalo. Utiliza-se mais uma vez E/S assíncrona para que seja sanada esta deficiência. No modo assíncrono um socket fica responsável por escutar chamadas de clientes, e outros sockets são instanciados para cada conexão, possibilitando múltiplas conexões. Para que seja criada uma conexão é necessário utilização da classe TCPListener que provê serviços TCP/IP de alto nível.

Para exemplificar a transmissão de dados através de uma rede utilizando streams, iremos criar um servidor e um cliente de streaming utilizando o protocolo TCP/IP através das classes TCPListener e TCPClient. Começaremos com a aplicação Servidor.

Iniciamos criando a classe ServidorArquivoAssincrono que irá representar o nosso servidor de arquivos. Esta classe tem como atributos membros: tamanhoBuffer, que ira armazenar o tamanho do buffer; buffer, um array de bytes que será o buffer utilizado pela classe; streamRede, que será a stream que utilizaremos para representar a rede por onde irão trafegar os dados; streamEntrada, que será a stream que irá representar o arquivo que será lido; callBackLeitura, delegate que irá "apontar" para um método que tomará alguma ação quando a leitura de determinados dados, vindos da rede, for concluída; callBackEscrita , delegate que irá "apontar" para um método que tomará alguma ação quando a escrita na rede de determinados dados, vindos de um arquivo, for concluída; e callBackLeituraArquivo, delegate que irá "apontar" para um método que tomará alguma ação quando a leitura de arquivo for concluída.

using System; using System.Net.Sockets; using System.Text; using System.IO; namespace Exemplo { public class ServidorArquivoAssincrono { private const int tamanhoBuffer = 256; private byte[] buffer; private Socket socket; private NetworkStream streamRede; private Stream streamEntrada; private AsyncCallback callBackLeitura; private AsyncCallback callBackEscrita; private AsyncCallback callBackLeituraArquivo; 

Criamos o método Main que sera o ponto de entrada da aplicação, que instancia um objeto da classe ServidorArquivoAssincrono e chama seu método Executar.

 public static void Main( ) { ServidorArquivoAssincrono servidorArquivoAssincrono = new ServidorArquivoAssincrono(); servidorArquivoAssincrono.Executar( ); } 

O método Executar instancia um objeto da classe TcpListener, passando para seu construtor o número da porta que ele irá escutar para depois iniciar a escuta através do método Start(). É criado um for "infinito" para que a aplicação fique de prontidão para possíveis conexões feitas por clientes. A conexão é aceita por intermédio do método AcceptSocket que retorna um socket e testada através da propriedade Connected. Esta propriedade retorna um valor booleano, true se conectado e false se não conectado. Caso conectado é instanciado um objeto da classe ManipulaCliente que ira representar a interação entre o servidor e o determinado cliente. Logo após é chamado então o método ObtemNomeArquivo.

private void Executar( ) { TcpListener tcpListener = new TcpListener(50000); tcpListener.Start( ); for (;;) { Socket socket = tcpListener.AcceptSocket( ); if (socket.Connected) { ManipulaCliente manipulaCliente = new ManipulaCliente (socket); manipulaCliente.ObtemNomeArquivo(); } } } 

A classe ManipulaCliente, já citada acima, foi criada para representar a abstração da interação entre o servidor e um determinado cliente conectado. O construtor desta classe recebe o socket do cliente, inicializa a variável membro socket com o mesmo, instancia um array de bytes e instancia um NetworkStream através do socket passado ao construtor de ManipulaCliente. O array de bytes será o buffer que irá conter os dados do arquivo a ser lido e o NetworkStream representará a rede que irá trafegar os dados entre o cliente e o servidor. Os delegates de callback também são instanciados passando os seus respectivos métodos que serão chamados quando determinadas tarefas forem concluídas, no caso leitura de arquivo, rede e escrita na rede.

class ManipulaCliente { public ManipulaCliente (Socket socket) { this.socket = socket; buffer = new byte[256]; streamRede = new NetworkStream(socket); callbackLeituraArquivo =new AsyncCallback(this.QuandoLeituraArquivoForConcluida); callbackLeitura = new AsyncCallback(this.QuandoLeituraForConcluida); callbackEscrita = new AsyncCallback(this.QuandoEscritaForConcluida); } 

O nome do arquivo, que é enviado pelo cliente através da rede, é obtido através de uma NetworkStream chamando o método ObtemNomeArquivo da classe ManipulaCliente.

 public void ObtemNomeArquivo ( ) { streamRede.BeginRead (buffer, 0, buffer.Length, callbackLeitura, null); } 

Quando a leitura da stream de rede for concluída é chamado o método QuandoLeituraForConcluida, através do delegate callbackLeitura, que verifica a quantidade de bytes lidos através da chamada do método EndRead da classe NetworkStream. Os bytes são transformados em uma String que é passada como argumento do método estático OpenRead, da classe File. É iniciada a leitura do arquivo através da stream streamEntrada.

 private void QuandoLeituraForConcluida ( IAsyncResult result ) { int bytesLidos = streamRede.EndRead(result); if( bytesLidos> 0 ) { string nomeArquivo = System.Text.Encoding.ASCII.GetString (buffer, 0, bytesLidos); streamEntrada = File.OpenRead(nomeArquivo); streamEntrada.BeginRead(buffer, 0, buffer.Length, callBackLeituraArquivo, null); } else { streamRede.Close( ); socket.Close( ); streamRede = null; streamRede = null; } } 

Quando a leitura do arquivo for concluída , ou seja, o buffer for preenchido, é chamado o método QuandoLeituraArquivoForConcluida, através do delegate de callback, callBackLeituraArquivo. Este método alavancará o processo de escrita dos dados na Rede através do objeto da classe NetworkStream, streamRede.

void QuandoLeituraArquivoForConcluida (IAsyncResult result) { int bytesLidos = streamEntrada.EndRead(result); if (bytesLidos> 0) { streamRede.BeginWrite(buffer, 0, bytesLidos, callbackEscrita, null); } } 

Quando o processo de escrita na rede for concluído, novamente é iniciado o processo de leitura do arquivo através do método QuandoEscritaForConcluida, chamado por intermédio do delegate de callback callbackEscrita .

 private void QuandoEscritaForConcluida ( IAsyncResult result) { streamRede.EndWrite(result); streamEntrada.BeginRead(buffer, 0,buffer.Length, callBackLeituraArquivo, null); } } } } 

Este procedimento de leitura e escrita continua até que não existam mais dados para serem lidos. O exemplo de aplicação servidora acima é relativamente simples, onde, primeiramente, o servidor requisita o nome do arquivo a ser lido e logo começa a leitura e escrita assíncrona deste arquivo. O ponto chave de aplicações assíncronas é o encadeamento dos procedimentos através dos delegates de callback. A seguir iremos construir a aplicação cliente para concluir a explanação do assunto. A aplicação cliente sera constituída pela classe ClienteArquivoAssincrono, que será responsável pelo envio de dados do cliente e a recepção de dados vindos do servidor. A classe contem os seguintes atributos membros : tamanhoBuffer, que armazenará o tamanho do buffer; buffer, que armazenara os dados; streamRede, que irá abstrair a rede que está entre o cliente e o servidor; e streamSaida, que será responsável pela escrita dos dados em um determinado arquivo no cliente.

using System; using System.Net.Sockets; using System.Threading; using System.Text; using System.Runtime.Serialization.Formatters.Binary; namespace Exemplo { public class ClienteArquivoAssincrono { private const int tamanhoBuffer = 256; private NetworkStream streamRede; private Stream streamSaida; static public int Main( ) { ClienteArquivoAssincrono clienteArquivoAssincrono = new clienteArquivoAssincrono ( ); return clienteArquivoAssincrono.Executa( ); } 

O construtor desta classe instancia um objeto da classe TcpClient, passando o número da porta e o nome do servidor, que a aplicação irá se tornar cliente. O atributo membro streamRede é instanciado através do método GetStream da classe TcpClient que retorna um Stream.

ClienteArquivoAssincrono ( ) { TcpClient tcpClient = new TcpClient("localhost", 50000); streamRede = tcpClient.GetStream( ); } 

Abaixo vemos o método Executa que envia para o servidor o nome do arquivo através da classe StreamWriter. StreamWriter, por sua vez escreve na streamRede que é a abstração da rede por onde serão enviados os dados para o servidor. Depois de feita a transferência dos dados do servidor para o cliente, o próximo passo é instanciar um objeto da classe Stream (streamSaida) através do método estático OpenWrite, da classe File, que sera utilizado para escrever os dados enviados pelo servidor em um arquivo.

private int Executa( ) { string nomeArquivo = "C:\\ArquivoNoServidor.txt"; StreamWriter writer = new StreamWriter(streamRede); writer.Write(nomeArquivo); writer.Flush( ); int bytesLidos = 0; streamSaida = File.OpenWrite(@"C:\ArquivoNoCliente.txt") while (!byteLidos.Convert.ToBoolean()) { char[] buffer = new char[tamanhoBuffer]; StreamReader streamEntrada = new StreamReader(streamRede); int bytesLidos = streamEntrada.Read(buffer,0,tamanhoBuffer); if (bytesLidos== 0 { Continue; } else { streamSaida.Write(buffer,0, tamanhoBuffer); } } streamRede.Close( ); return 0; } } 

streams_3.jpg

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.