diff --git a/CMakeLists.txt b/CMakeLists.txt index 215139a26..4a0b298e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ if(NOT WIN32) find_suggested_package(ALSA) find_optional_package(PulseAudio) find_suggested_package(Cups) + find_suggested_package(FFmpeg) endif() # Endian diff --git a/channels/drdynvc/tsmf/CMakeLists.txt b/channels/drdynvc/tsmf/CMakeLists.txt index 98e25058d..7df0e3a35 100644 --- a/channels/drdynvc/tsmf/CMakeLists.txt +++ b/channels/drdynvc/tsmf/CMakeLists.txt @@ -43,3 +43,15 @@ target_link_libraries(tsmf freerdp-utils) install(TARGETS tsmf DESTINATION ${FREERDP_PLUGIN_PATH}) +if(FFMPEG_FOUND) + add_subdirectory(ffmpeg) +endif() + +if(ALSA_FOUND) + add_subdirectory(alsa) +endif() + +if(PULSE_FOUND) + add_subdirectory(pulse) +endif() + diff --git a/channels/drdynvc/tsmf/alsa/CMakeLists.txt b/channels/drdynvc/tsmf/alsa/CMakeLists.txt new file mode 100644 index 000000000..c769a1857 --- /dev/null +++ b/channels/drdynvc/tsmf/alsa/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(TSMF_ALSA_SRCS + tsmf_alsa.c +) + +include_directories(..) +include_directories(${ALSA_INCLUDE_DIRS}) + +add_library(tsmf_alsa ${TSMF_ALSA_SRCS}) +set_target_properties(tsmf_alsa PROPERTIES PREFIX "") + +target_link_libraries(tsmf_alsa freerdp-utils) +target_link_libraries(tsmf_alsa ${ALSA_LIBRARIES}) + +install(TARGETS tsmf_alsa DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/alsa/tsmf_alsa.c b/channels/drdynvc/tsmf/alsa/tsmf_alsa.c new file mode 100644 index 000000000..39bc8b7a4 --- /dev/null +++ b/channels/drdynvc/tsmf/alsa/tsmf_alsa.c @@ -0,0 +1,264 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - ALSA Audio Device + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFALSAAudioDevice +{ + ITSMFAudioDevice iface; + + char device[32]; + snd_pcm_t* out_handle; + uint32 source_rate; + uint32 actual_rate; + uint32 source_channels; + uint32 actual_channels; + uint32 bytes_per_sample; +} TSMFALSAAudioDevice; + +static boolean tsmf_alsa_open_device(TSMFALSAAudioDevice* alsa) +{ + int error; + + error = snd_pcm_open(&alsa->out_handle, alsa->device, SND_PCM_STREAM_PLAYBACK, 0); + if (error < 0) + { + DEBUG_WARN("failed to open device %s", alsa->device); + return False; + } + + DEBUG_DVC("open device %s", alsa->device); + return True; +} + +static boolean tsmf_alsa_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + if (!device) + { + if (!alsa->device[0]) + strcpy(alsa->device, "default"); + } + else + { + strcpy(alsa->device, device); + } + + return tsmf_alsa_open_device(alsa); +} + +static boolean tsmf_alsa_set_format(ITSMFAudioDevice* audio, + uint32 sample_rate, uint32 channels, uint32 bits_per_sample) +{ + int error; + snd_pcm_uframes_t frames; + snd_pcm_hw_params_t* hw_params; + snd_pcm_sw_params_t* sw_params; + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + if (!alsa->out_handle) + return False; + + snd_pcm_drop(alsa->out_handle); + + alsa->actual_rate = alsa->source_rate = sample_rate; + alsa->actual_channels = alsa->source_channels = channels; + alsa->bytes_per_sample = bits_per_sample / 8; + + error = snd_pcm_hw_params_malloc(&hw_params); + if (error < 0) + { + DEBUG_WARN("snd_pcm_hw_params_malloc failed"); + return False; + } + snd_pcm_hw_params_any(alsa->out_handle, hw_params); + snd_pcm_hw_params_set_access(alsa->out_handle, hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(alsa->out_handle, hw_params, + SND_PCM_FORMAT_S16_LE); + snd_pcm_hw_params_set_rate_near(alsa->out_handle, hw_params, + &alsa->actual_rate, NULL); + snd_pcm_hw_params_set_channels_near(alsa->out_handle, hw_params, + &alsa->actual_channels); + frames = sample_rate; + snd_pcm_hw_params_set_buffer_size_near(alsa->out_handle, hw_params, + &frames); + snd_pcm_hw_params(alsa->out_handle, hw_params); + snd_pcm_hw_params_free(hw_params); + + error = snd_pcm_sw_params_malloc(&sw_params); + if (error < 0) + { + DEBUG_WARN("snd_pcm_sw_params_malloc"); + return False; + } + snd_pcm_sw_params_current(alsa->out_handle, sw_params); + snd_pcm_sw_params_set_start_threshold(alsa->out_handle, sw_params, + frames / 2); + snd_pcm_sw_params(alsa->out_handle, sw_params); + snd_pcm_sw_params_free(sw_params); + + snd_pcm_prepare(alsa->out_handle); + + DEBUG_DVC("sample_rate %d channels %d bits_per_sample %d", + sample_rate, channels, bits_per_sample); + DEBUG_DVC("hardware buffer %d frames", (int)frames); + if ((alsa->actual_rate != alsa->source_rate) || + (alsa->actual_channels != alsa->source_channels)) + { + DEBUG_DVC("actual rate %d / channel %d is different " + "from source rate %d / channel %d, resampling required.", + alsa->actual_rate, alsa->actual_channels, + alsa->source_rate, alsa->source_channels); + } + return True; +} + +static boolean tsmf_alsa_play(ITSMFAudioDevice* audio, uint8* data, uint32 data_size) +{ + int len; + int error; + int frames; + uint8* end; + uint8* src; + uint8* pindex; + int rbytes_per_frame; + int sbytes_per_frame; + uint8* resampled_data; + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + DEBUG_DVC("data_size %d", data_size); + + if (alsa->out_handle) + { + sbytes_per_frame = alsa->source_channels * alsa->bytes_per_sample; + rbytes_per_frame = alsa->actual_channels * alsa->bytes_per_sample; + + if ((alsa->source_rate == alsa->actual_rate) && + (alsa->source_channels == alsa->actual_channels)) + { + resampled_data = NULL; + src = data; + data_size = data_size; + } + else + { + resampled_data = dsp_resample(data, alsa->bytes_per_sample, + alsa->source_channels, alsa->source_rate, data_size / sbytes_per_frame, + alsa->actual_channels, alsa->actual_rate, &frames); + DEBUG_DVC("resampled %d frames at %d to %d frames at %d", + data_size / sbytes_per_frame, alsa->source_rate, frames, alsa->actual_rate); + data_size = frames * rbytes_per_frame; + src = resampled_data; + } + + pindex = src; + end = pindex + data_size; + while (pindex < end) + { + len = end - pindex; + frames = len / rbytes_per_frame; + error = snd_pcm_writei(alsa->out_handle, pindex, frames); + if (error == -EPIPE) + { + snd_pcm_recover(alsa->out_handle, error, 0); + error = 0; + } + else if (error < 0) + { + DEBUG_DVC("error len %d", error); + snd_pcm_close(alsa->out_handle); + alsa->out_handle = 0; + tsmf_alsa_open_device(alsa); + break; + } + DEBUG_DVC("%d frames played.", error); + if (error == 0) + break; + pindex += error * rbytes_per_frame; + } + + if (resampled_data) + xfree(resampled_data); + } + xfree(data); + + return True; +} + +static uint64 tsmf_alsa_get_latency(ITSMFAudioDevice* audio) +{ + uint64 latency = 0; + snd_pcm_sframes_t frames = 0; + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + if (alsa->out_handle && alsa->actual_rate > 0 && + snd_pcm_delay(alsa->out_handle, &frames) == 0 && + frames > 0) + { + latency = ((uint64)frames) * 10000000LL / (uint64)alsa->actual_rate; + } + return latency; +} + +static void tsmf_alsa_flush(ITSMFAudioDevice* audio) +{ +} + +static void tsmf_alsa_free(ITSMFAudioDevice* audio) +{ + TSMFALSAAudioDevice* alsa = (TSMFALSAAudioDevice*) audio; + + DEBUG_DVC(""); + + if (alsa->out_handle) + { + snd_pcm_drain(alsa->out_handle); + snd_pcm_close(alsa->out_handle); + } + xfree(alsa); +} + +ITSMFAudioDevice* TSMFAudioDeviceEntry(void) +{ + TSMFALSAAudioDevice* alsa; + + alsa = xnew(TSMFALSAAudioDevice); + + alsa->iface.Open = tsmf_alsa_open; + alsa->iface.SetFormat = tsmf_alsa_set_format; + alsa->iface.Play = tsmf_alsa_play; + alsa->iface.GetLatency = tsmf_alsa_get_latency; + alsa->iface.Flush = tsmf_alsa_flush; + alsa->iface.Free = tsmf_alsa_free; + + return (ITSMFAudioDevice*) alsa; +} + diff --git a/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt b/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt new file mode 100644 index 000000000..ea0b1010b --- /dev/null +++ b/channels/drdynvc/tsmf/ffmpeg/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(TSMF_FFMPEG_SRCS + tsmf_ffmpeg.c +) + +include_directories(..) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +add_library(tsmf_ffmpeg ${TSMF_FFMPEG_SRCS}) +set_target_properties(tsmf_ffmpeg PROPERTIES PREFIX "") + +target_link_libraries(tsmf_ffmpeg freerdp-utils) +target_link_libraries(tsmf_ffmpeg ${FFMPEG_LIBRARIES}) + +install(TARGETS tsmf_ffmpeg DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c new file mode 100644 index 000000000..a41187fb0 --- /dev/null +++ b/channels/drdynvc/tsmf/ffmpeg/tsmf_ffmpeg.c @@ -0,0 +1,514 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - FFmpeg Decoder + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "tsmf_constants.h" +#include "tsmf_decoder.h" + +/* Compatibility with older FFmpeg */ +#if LIBAVUTIL_VERSION_MAJOR < 50 +#define AVMEDIA_TYPE_VIDEO 0 +#define AVMEDIA_TYPE_AUDIO 1 +#endif + +typedef struct _TSMFFFmpegDecoder +{ + ITSMFDecoder iface; + + int media_type; + enum CodecID codec_id; + AVCodecContext* codec_context; + AVCodec* codec; + AVFrame* frame; + int prepared; + + uint8* decoded_data; + uint32 decoded_size; + uint32 decoded_size_max; +} TSMFFFmpegDecoder; + +static boolean tsmf_ffmpeg_init_context(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context = avcodec_alloc_context(); + if (!mdecoder->codec_context) + { + DEBUG_WARN("avcodec_alloc_context failed."); + return False; + } + + return True; +} + +static boolean tsmf_ffmpeg_init_video_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context->width = media_type->Width; + mdecoder->codec_context->height = media_type->Height; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->time_base.den = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->time_base.num = media_type->SamplesPerSecond.Denominator; + + mdecoder->frame = avcodec_alloc_frame(); + + return True; +} + +static boolean tsmf_ffmpeg_init_audio_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + mdecoder->codec_context->sample_rate = media_type->SamplesPerSecond.Numerator; + mdecoder->codec_context->bit_rate = media_type->BitRate; + mdecoder->codec_context->channels = media_type->Channels; + mdecoder->codec_context->block_align = media_type->BlockAlign; + +#ifdef AV_CPU_FLAG_SSE2 + mdecoder->codec_context->dsp_mask = AV_CPU_FLAG_SSE2 | AV_CPU_FLAG_MMX2; +#else + mdecoder->codec_context->dsp_mask = FF_MM_SSE2 | FF_MM_MMX2; +#endif + + return True; +} + +static boolean tsmf_ffmpeg_init_stream(ITSMFDecoder* decoder, const TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + uint32 size; + const uint8* s; + uint8* p; + + mdecoder->codec = avcodec_find_decoder(mdecoder->codec_id); + if (!mdecoder->codec) + { + DEBUG_WARN("avcodec_find_decoder failed."); + return False; + } + + mdecoder->codec_context->codec_id = mdecoder->codec_id; + mdecoder->codec_context->codec_type = mdecoder->media_type; + + if (mdecoder->media_type == AVMEDIA_TYPE_VIDEO) + { + if (!tsmf_ffmpeg_init_video_stream(decoder, media_type)) + return False; + } + else if (mdecoder->media_type == AVMEDIA_TYPE_AUDIO) + { + if (!tsmf_ffmpeg_init_audio_stream(decoder, media_type)) + return False; + } + + if (media_type->ExtraData) + { + if (media_type->SubType == TSMF_SUB_TYPE_AVC1 && + media_type->FormatType == TSMF_FORMAT_TYPE_MPEG2VIDEOINFO) + { + /* The extradata format that FFmpeg uses is following CodecPrivate in Matroska. + See http://haali.su/mkv/codecs.pdf */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = xzalloc(mdecoder->codec_context->extradata_size); + p = mdecoder->codec_context->extradata; + *p++ = 1; /* Reserved? */ + *p++ = media_type->ExtraData[8]; /* Profile */ + *p++ = 0; /* Profile */ + *p++ = media_type->ExtraData[12]; /* Level */ + *p++ = 0xff; /* Flag? */ + *p++ = 0xe0 | 0x01; /* Reserved | #sps */ + s = media_type->ExtraData + 20; + size = ((uint32)(*s)) * 256 + ((uint32)(*(s + 1))); + memcpy(p, s, size + 2); + s += size + 2; + p += size + 2; + *p++ = 1; /* #pps */ + size = ((uint32)(*s)) * 256 + ((uint32)(*(s + 1))); + memcpy(p, s, size + 2); + } + else + { + /* Add a padding to avoid invalid memory read in some codec */ + mdecoder->codec_context->extradata_size = media_type->ExtraDataSize + 8; + mdecoder->codec_context->extradata = xzalloc(mdecoder->codec_context->extradata_size); + memcpy(mdecoder->codec_context->extradata, media_type->ExtraData, media_type->ExtraDataSize); + memset(mdecoder->codec_context->extradata + media_type->ExtraDataSize, 0, 8); + } + } + + if (mdecoder->codec->capabilities & CODEC_CAP_TRUNCATED) + mdecoder->codec_context->flags |= CODEC_FLAG_TRUNCATED; + + return True; +} + +static boolean tsmf_ffmpeg_prepare(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (avcodec_open(mdecoder->codec_context, mdecoder->codec) < 0) + { + DEBUG_WARN("avcodec_open failed."); + return False; + } + + mdecoder->prepared = 1; + + return True; +} + +static boolean tsmf_ffmpeg_set_format(ITSMFDecoder* decoder, TS_AM_MEDIA_TYPE* media_type) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + switch (media_type->MajorType) + { + case TSMF_MAJOR_TYPE_VIDEO: + mdecoder->media_type = AVMEDIA_TYPE_VIDEO; + break; + case TSMF_MAJOR_TYPE_AUDIO: + mdecoder->media_type = AVMEDIA_TYPE_AUDIO; + break; + default: + return False; + } + switch (media_type->SubType) + { + case TSMF_SUB_TYPE_WVC1: + mdecoder->codec_id = CODEC_ID_VC1; + break; + case TSMF_SUB_TYPE_WMA2: + mdecoder->codec_id = CODEC_ID_WMAV2; + break; + case TSMF_SUB_TYPE_WMA9: + mdecoder->codec_id = CODEC_ID_WMAPRO; + break; + case TSMF_SUB_TYPE_MP3: + mdecoder->codec_id = CODEC_ID_MP3; + break; + case TSMF_SUB_TYPE_MP2A: + mdecoder->codec_id = CODEC_ID_MP2; + break; + case TSMF_SUB_TYPE_MP2V: + mdecoder->codec_id = CODEC_ID_MPEG2VIDEO; + break; + case TSMF_SUB_TYPE_WMV3: + mdecoder->codec_id = CODEC_ID_WMV3; + break; + case TSMF_SUB_TYPE_AAC: + mdecoder->codec_id = CODEC_ID_AAC; + /* For AAC the pFormat is a HEAACWAVEINFO struct, and the codec data + is at the end of it. See + http://msdn.microsoft.com/en-us/library/dd757806.aspx */ + if (media_type->ExtraData) + { + media_type->ExtraData += 12; + media_type->ExtraDataSize -= 12; + } + break; + case TSMF_SUB_TYPE_H264: + case TSMF_SUB_TYPE_AVC1: + mdecoder->codec_id = CODEC_ID_H264; + break; + case TSMF_SUB_TYPE_AC3: + mdecoder->codec_id = CODEC_ID_AC3; + break; + default: + return False; + } + + if (!tsmf_ffmpeg_init_context(decoder)) + return False; + if (!tsmf_ffmpeg_init_stream(decoder, media_type)) + return False; + if (!tsmf_ffmpeg_prepare(decoder)) + return False; + + return True; +} + +static boolean tsmf_ffmpeg_decode_video(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int decoded; + int len; + AVFrame* frame; + boolean ret = True; + +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_video(mdecoder->codec_context, mdecoder->frame, &decoded, data, data_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (uint8*) data; + pkt.size = data_size; + if (extensions & TSMM_SAMPLE_EXT_CLEANPOINT) + pkt.flags |= AV_PKT_FLAG_KEY; + len = avcodec_decode_video2(mdecoder->codec_context, mdecoder->frame, &decoded, &pkt); + } +#endif + + if (len < 0) + { + DEBUG_WARN("data_size %d, avcodec_decode_video failed (%d)", data_size, len); + ret = False; + } + else if (!decoded) + { + DEBUG_WARN("data_size %d, no frame is decoded.", data_size); + ret = False; + } + else + { + DEBUG_DVC("linesize[0] %d linesize[1] %d linesize[2] %d linesize[3] %d " + "pix_fmt %d width %d height %d", + mdecoder->frame->linesize[0], mdecoder->frame->linesize[1], + mdecoder->frame->linesize[2], mdecoder->frame->linesize[3], + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + + mdecoder->decoded_size = avpicture_get_size(mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + mdecoder->decoded_data = xzalloc(mdecoder->decoded_size); + frame = avcodec_alloc_frame(); + avpicture_fill((AVPicture *) frame, mdecoder->decoded_data, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + + av_picture_copy((AVPicture *) frame, (AVPicture *) mdecoder->frame, + mdecoder->codec_context->pix_fmt, + mdecoder->codec_context->width, mdecoder->codec_context->height); + + av_free(frame); + } + + return ret; +} + +static boolean tsmf_ffmpeg_decode_audio(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + int len; + int frame_size; + uint32 src_size; + const uint8* src; + uint8* dst; + int dst_offset; + +#if 0 + LLOGLN(0, ("tsmf_ffmpeg_decode_audio: data_size %d", data_size)); + int i; + for (i = 0; i < data_size; i++) + { + LLOG(0, ("%02X ", data[i])); + if (i % 16 == 15) + LLOG(0, ("\n")); + } + LLOG(0, ("\n")); +#endif + + if (mdecoder->decoded_size_max == 0) + mdecoder->decoded_size_max = AVCODEC_MAX_AUDIO_FRAME_SIZE + 16; + mdecoder->decoded_data = xzalloc(mdecoder->decoded_size_max); + /* align the memory for SSE2 needs */ + dst = (uint8*) (((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); + dst_offset = dst - mdecoder->decoded_data; + src = data; + src_size = data_size; + + while (src_size > 0) + { + /* Ensure enough space for decoding */ + if (mdecoder->decoded_size_max - mdecoder->decoded_size < AVCODEC_MAX_AUDIO_FRAME_SIZE) + { + mdecoder->decoded_size_max *= 2; + mdecoder->decoded_data = xrealloc(mdecoder->decoded_data, mdecoder->decoded_size_max); + dst = (uint8*) (((uintptr_t)mdecoder->decoded_data + 15) & ~ 0x0F); + if (dst - mdecoder->decoded_data != dst_offset) + { + /* re-align the memory if the alignment has changed after realloc */ + memmove(dst, mdecoder->decoded_data + dst_offset, mdecoder->decoded_size); + dst_offset = dst - mdecoder->decoded_data; + } + dst += mdecoder->decoded_size; + } + frame_size = mdecoder->decoded_size_max - mdecoder->decoded_size; +#if LIBAVCODEC_VERSION_MAJOR < 52 || (LIBAVCODEC_VERSION_MAJOR == 52 && LIBAVCODEC_VERSION_MINOR <= 20) + len = avcodec_decode_audio2(mdecoder->codec_context, + (int16_t*) dst, &frame_size, + src, src_size); +#else + { + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = (uint8*) src; + pkt.size = src_size; + len = avcodec_decode_audio3(mdecoder->codec_context, + (int16_t*) dst, &frame_size, &pkt); + } +#endif + if (len <= 0 || frame_size <= 0) + { + DEBUG_WARN("error decoding"); + break; + } + src += len; + src_size -= len; + mdecoder->decoded_size += frame_size; + dst += frame_size; + } + + if (mdecoder->decoded_size == 0) + { + xfree(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + else if (dst_offset) + { + /* move the aligned decoded data to original place */ + memmove(mdecoder->decoded_data, dst, mdecoder->decoded_size); + } + + DEBUG_DVC("data_size %d decoded_size %d", + data_size, mdecoder->decoded_size); + + return True; +} + +static boolean tsmf_ffmpeg_decode(ITSMFDecoder* decoder, const uint8* data, uint32 data_size, uint32 extensions) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->decoded_data) + { + xfree(mdecoder->decoded_data); + mdecoder->decoded_data = NULL; + } + mdecoder->decoded_size = 0; + + switch (mdecoder->media_type) + { + case AVMEDIA_TYPE_VIDEO: + return tsmf_ffmpeg_decode_video(decoder, data, data_size, extensions); + case AVMEDIA_TYPE_AUDIO: + return tsmf_ffmpeg_decode_audio(decoder, data, data_size, extensions); + default: + DEBUG_WARN("unknown media type."); + return False; + } +} + +static uint8* tsmf_ffmpeg_get_decoded_data(ITSMFDecoder* decoder, uint32* size) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + uint8* buf; + + *size = mdecoder->decoded_size; + buf = mdecoder->decoded_data; + mdecoder->decoded_data = NULL; + mdecoder->decoded_size = 0; + return buf; +} + +static uint32 tsmf_ffmpeg_get_decoded_format(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + switch (mdecoder->codec_context->pix_fmt) + { + case PIX_FMT_YUV420P: + return RDP_PIXFMT_I420; + + default: + DEBUG_WARN("unsupported pixel format %u", + mdecoder->codec_context->pix_fmt); + return (uint32) -1; + } +} + +static boolean tsmf_ffmpeg_get_decoded_dimension(ITSMFDecoder* decoder, uint32* width, uint32* height) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->codec_context->width > 0 && mdecoder->codec_context->height > 0) + { + *width = mdecoder->codec_context->width; + *height = mdecoder->codec_context->height; + return True; + } + else + { + return False; + } +} + +static void tsmf_ffmpeg_free(ITSMFDecoder* decoder) +{ + TSMFFFmpegDecoder* mdecoder = (TSMFFFmpegDecoder*) decoder; + + if (mdecoder->frame) + av_free(mdecoder->frame); + if (mdecoder->decoded_data) + xfree(mdecoder->decoded_data); + if (mdecoder->codec_context) + { + if (mdecoder->prepared) + avcodec_close(mdecoder->codec_context); + if (mdecoder->codec_context->extradata) + xfree(mdecoder->codec_context->extradata); + av_free(mdecoder->codec_context); + } + xfree(decoder); +} + +static boolean initialized = False; + +ITSMFDecoder* +TSMFDecoderEntry(void) +{ + TSMFFFmpegDecoder * decoder; + + if (!initialized) + { + avcodec_init(); + avcodec_register_all(); + initialized = True; + } + + decoder = xnew(TSMFFFmpegDecoder); + + decoder->iface.SetFormat = tsmf_ffmpeg_set_format; + decoder->iface.Decode = tsmf_ffmpeg_decode; + decoder->iface.GetDecodedData = tsmf_ffmpeg_get_decoded_data; + decoder->iface.GetDecodedFormat = tsmf_ffmpeg_get_decoded_format; + decoder->iface.GetDecodedDimension = tsmf_ffmpeg_get_decoded_dimension; + decoder->iface.Free = tsmf_ffmpeg_free; + + return (ITSMFDecoder*) decoder; +} + diff --git a/channels/drdynvc/tsmf/pulse/CMakeLists.txt b/channels/drdynvc/tsmf/pulse/CMakeLists.txt new file mode 100644 index 000000000..373d4d32b --- /dev/null +++ b/channels/drdynvc/tsmf/pulse/CMakeLists.txt @@ -0,0 +1,34 @@ +# FreeRDP: A Remote Desktop Protocol Client +# FreeRDP cmake build script +# +# Copyright 2011 O.S. Systems Software Ltda. +# Copyright 2011 Otavio Salvador +# Copyright 2011 Marc-Andre Moreau +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set(TSMF_PULSE_SRCS + tsmf_pulse.c +) + +include_directories(..) +include_directories(${PULSE_INCLUDE_DIRS}) + +add_library(tsmf_pulse ${TSMF_PULSE_SRCS}) +set_target_properties(tsmf_pulse PROPERTIES PREFIX "") + +target_link_libraries(tsmf_pulse freerdp-utils) +target_link_libraries(tsmf_pulse ${PULSE_LIBRARIES}) + +install(TARGETS tsmf_pulse DESTINATION ${FREERDP_PLUGIN_PATH}) + diff --git a/channels/drdynvc/tsmf/pulse/tsmf_pulse.c b/channels/drdynvc/tsmf/pulse/tsmf_pulse.c new file mode 100644 index 000000000..4d4ecee25 --- /dev/null +++ b/channels/drdynvc/tsmf/pulse/tsmf_pulse.c @@ -0,0 +1,402 @@ +/** + * FreeRDP: A Remote Desktop Protocol client. + * Video Redirection Virtual Channel - PulseAudio Device + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include "tsmf_audio.h" + +typedef struct _TSMFPulseAudioDevice +{ + ITSMFAudioDevice iface; + + char device[32]; + pa_threaded_mainloop* mainloop; + pa_context* context; + pa_sample_spec sample_spec; + pa_stream* stream; +} TSMFPulseAudioDevice; + +static void tsmf_pulse_context_state_callback(pa_context* context, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_context_state_t state; + + state = pa_context_get_state(context); + switch (state) + { + case PA_CONTEXT_READY: + DEBUG_DVC("PA_CONTEXT_READY"); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + case PA_CONTEXT_FAILED: + case PA_CONTEXT_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal(pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static boolean tsmf_pulse_connect(TSMFPulseAudioDevice* pulse) +{ + pa_context_state_t state; + + if (!pulse->context) + return False; + + if (pa_context_connect(pulse->context, NULL, 0, NULL)) + { + DEBUG_WARN("pa_context_connect failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + pa_threaded_mainloop_lock(pulse->mainloop); + if (pa_threaded_mainloop_start(pulse->mainloop) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_threaded_mainloop_start failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + for (;;) + { + state = pa_context_get_state(pulse->context); + if (state == PA_CONTEXT_READY) + break; + if (!PA_CONTEXT_IS_GOOD(state)) + { + DEBUG_DVC("bad context state (%d)", + pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_CONTEXT_READY) + { + DEBUG_DVC("connected"); + return True; + } + else + { + pa_context_disconnect(pulse->context); + return False; + } +} + +static boolean tsmf_pulse_open(ITSMFAudioDevice* audio, const char* device) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (device) + { + strcpy(pulse->device, device); + } + + pulse->mainloop = pa_threaded_mainloop_new(); + if (!pulse->mainloop) + { + DEBUG_WARN("pa_threaded_mainloop_new failed"); + return False; + } + pulse->context = pa_context_new(pa_threaded_mainloop_get_api(pulse->mainloop), "freerdp"); + if (!pulse->context) + { + DEBUG_WARN("pa_context_new failed"); + return False; + } + pa_context_set_state_callback(pulse->context, tsmf_pulse_context_state_callback, pulse); + if (tsmf_pulse_connect(pulse)) + { + DEBUG_WARN("tsmf_pulse_connect failed"); + return False; + } + + DEBUG_DVC("open device %s", pulse->device); + return True; +} + +static void tsmf_pulse_stream_success_callback(pa_stream* stream, int success, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static void tsmf_pulse_wait_for_operation(TSMFPulseAudioDevice* pulse, pa_operation* operation) +{ + if (operation == NULL) + return; + while (pa_operation_get_state(operation) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_operation_unref(operation); +} + +static void tsmf_pulse_stream_state_callback(pa_stream* stream, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + pa_stream_state_t state; + + state = pa_stream_get_state(stream); + switch (state) + { + case PA_STREAM_READY: + DEBUG_DVC("PA_STREAM_READY"); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + DEBUG_DVC("state %d", (int)state); + pa_threaded_mainloop_signal (pulse->mainloop, 0); + break; + + default: + DEBUG_DVC("state %d", (int)state); + break; + } +} + +static void tsmf_pulse_stream_request_callback(pa_stream* stream, size_t length, void* userdata) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) userdata; + + DEBUG_DVC("%d", (int) length); + + pa_threaded_mainloop_signal(pulse->mainloop, 0); +} + +static boolean tsmf_pulse_close_stream(TSMFPulseAudioDevice* pulse) +{ + if (!pulse->context || !pulse->stream) + return False; + + DEBUG_DVC(""); + + pa_threaded_mainloop_lock(pulse->mainloop); + pa_stream_set_write_callback(pulse->stream, NULL, NULL); + tsmf_pulse_wait_for_operation(pulse, + pa_stream_drain(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_stream_disconnect(pulse->stream); + pa_stream_unref(pulse->stream); + pulse->stream = NULL; + pa_threaded_mainloop_unlock(pulse->mainloop); + + return True; +} + +static boolean tsmf_pulse_open_stream(TSMFPulseAudioDevice* pulse) +{ + pa_stream_state_t state; + pa_buffer_attr buffer_attr = { 0 }; + + if (!pulse->context) + return False; + + DEBUG_DVC(""); + + pa_threaded_mainloop_lock(pulse->mainloop); + pulse->stream = pa_stream_new(pulse->context, "freerdp", + &pulse->sample_spec, NULL); + if (!pulse->stream) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_new failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + pa_stream_set_state_callback(pulse->stream, + tsmf_pulse_stream_state_callback, pulse); + pa_stream_set_write_callback(pulse->stream, + tsmf_pulse_stream_request_callback, pulse); + buffer_attr.maxlength = pa_usec_to_bytes(500000, &pulse->sample_spec); + buffer_attr.tlength = pa_usec_to_bytes(250000, &pulse->sample_spec); + buffer_attr.prebuf = (uint32_t) -1; + buffer_attr.minreq = (uint32_t) -1; + buffer_attr.fragsize = (uint32_t) -1; + if (pa_stream_connect_playback(pulse->stream, + pulse->device[0] ? pulse->device : NULL, &buffer_attr, + PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE, + NULL, NULL) < 0) + { + pa_threaded_mainloop_unlock(pulse->mainloop); + DEBUG_WARN("pa_stream_connect_playback failed (%d)", + pa_context_errno(pulse->context)); + return False; + } + + for (;;) + { + state = pa_stream_get_state(pulse->stream); + if (state == PA_STREAM_READY) + break; + if (!PA_STREAM_IS_GOOD(state)) + { + DEBUG_WARN("bad stream state (%d)", + pa_context_errno(pulse->context)); + break; + } + pa_threaded_mainloop_wait(pulse->mainloop); + } + pa_threaded_mainloop_unlock(pulse->mainloop); + if (state == PA_STREAM_READY) + { + DEBUG_DVC("connected"); + return True; + } + else + { + tsmf_pulse_close_stream(pulse); + return False; + } +} + +static boolean tsmf_pulse_set_format(ITSMFAudioDevice* audio, + uint32 sample_rate, uint32 channels, uint32 bits_per_sample) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + DEBUG_DVC("sample_rate %d channels %d bits_per_sample %d", + sample_rate, channels, bits_per_sample); + + pulse->sample_spec.rate = sample_rate; + pulse->sample_spec.channels = channels; + pulse->sample_spec.format = PA_SAMPLE_S16LE; + + return tsmf_pulse_open_stream(pulse); +} + +static boolean tsmf_pulse_play(ITSMFAudioDevice* audio, uint8* data, uint32 data_size) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + uint8* src; + int len; + int ret; + + DEBUG_DVC("data_size %d", data_size); + + if (pulse->stream) + { + pa_threaded_mainloop_lock(pulse->mainloop); + + src = data; + while (data_size > 0) + { + while ((len = pa_stream_writable_size(pulse->stream)) == 0) + { + DEBUG_DVC("waiting"); + pa_threaded_mainloop_wait(pulse->mainloop); + } + if (len < 0) + break; + if (len > data_size) + len = data_size; + ret = pa_stream_write(pulse->stream, src, len, NULL, 0LL, PA_SEEK_RELATIVE); + if (ret < 0) + { + DEBUG_DVC("pa_stream_write failed (%d)", + pa_context_errno(pulse->context)); + break; + } + src += len; + data_size -= len; + } + + pa_threaded_mainloop_unlock(pulse->mainloop); + } + xfree(data); + + return True; +} + +static uint64 tsmf_pulse_get_latency(ITSMFAudioDevice* audio) +{ + pa_usec_t usec; + uint64 latency = 0; + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + if (pulse->stream && pa_stream_get_latency(pulse->stream, &usec, NULL) == 0) + { + latency = ((uint64)usec) * 10LL; + } + return latency; +} + +static void tsmf_pulse_flush(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + pa_threaded_mainloop_lock(pulse->mainloop); + tsmf_pulse_wait_for_operation(pulse, + pa_stream_flush(pulse->stream, tsmf_pulse_stream_success_callback, pulse)); + pa_threaded_mainloop_unlock(pulse->mainloop); +} + +static void tsmf_pulse_free(ITSMFAudioDevice* audio) +{ + TSMFPulseAudioDevice* pulse = (TSMFPulseAudioDevice*) audio; + + DEBUG_DVC(""); + + tsmf_pulse_close_stream(pulse); + if (pulse->mainloop) + { + pa_threaded_mainloop_stop(pulse->mainloop); + } + if (pulse->context) + { + pa_context_disconnect(pulse->context); + pa_context_unref(pulse->context); + pulse->context = NULL; + } + if (pulse->mainloop) + { + pa_threaded_mainloop_free(pulse->mainloop); + pulse->mainloop = NULL; + } + xfree(pulse); +} + +ITSMFAudioDevice* TSMFAudioDeviceEntry(void) +{ + TSMFPulseAudioDevice* pulse; + + pulse = xnew(TSMFPulseAudioDevice); + + pulse->iface.Open = tsmf_pulse_open; + pulse->iface.SetFormat = tsmf_pulse_set_format; + pulse->iface.Play = tsmf_pulse_play; + pulse->iface.GetLatency = tsmf_pulse_get_latency; + pulse->iface.Flush = tsmf_pulse_flush; + pulse->iface.Free = tsmf_pulse_free; + + return (ITSMFAudioDevice*) pulse; +} + diff --git a/cmake/FindFFmpeg.cmake b/cmake/FindFFmpeg.cmake new file mode 100644 index 000000000..c81f1132c --- /dev/null +++ b/cmake/FindFFmpeg.cmake @@ -0,0 +1 @@ +pkg_check_modules(FFMPEG libavcodec) diff --git a/include/freerdp/plugins/tsmf.h b/include/freerdp/plugins/tsmf.h index 18bc6fb39..6df4964b3 100644 --- a/include/freerdp/plugins/tsmf.h +++ b/include/freerdp/plugins/tsmf.h @@ -56,4 +56,9 @@ struct _RDP_REDRAW_EVENT }; typedef struct _RDP_REDRAW_EVENT RDP_REDRAW_EVENT; +/* RDP_VIDEO_FRAME_EVENT.frame_pixfmt */ +/* http://www.fourcc.org/yuv.php */ +#define RDP_PIXFMT_I420 0x30323449 +#define RDP_PIXFMT_YV12 0x32315659 + #endif /* __TSMF_PLUGIN */