Unholy melange of stuff; setup a standard test config; send / receive subs to / from...
authorCarl Hetherington <cth@carlh.net>
Mon, 15 Oct 2012 22:19:27 +0000 (23:19 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 15 Oct 2012 22:19:27 +0000 (23:19 +0100)
13 files changed:
src/lib/dcp_video_frame.cc
src/lib/decoder.cc
src/lib/film.cc
src/lib/film.h
src/lib/image.cc
src/lib/image.h
src/lib/imagemagick_encoder.cc
src/lib/server.cc
src/lib/subtitle.cc
src/lib/subtitle.h
src/lib/util.h
src/wx/film_viewer.cc
test/test.cc

index 04735269cde58f68d5f35b1b50d1035d70484727..e074b1cd16cf7955ea2fe46a160a1ac37fc4e7ee 100644 (file)
@@ -161,17 +161,14 @@ DCPVideoFrame::encode_locally ()
        shared_ptr<Image> prepared = _input->scale_and_convert_to_rgb (_out_size, _padding, _scaler);
 
        if (_subtitle) {
-               list<shared_ptr<SubtitleImage> > subs = _subtitle->images ();
-               for (list<shared_ptr<SubtitleImage> >::iterator i = subs.begin(); i != subs.end(); ++i) {
-                       Rectangle tx = transformed_subtitle_area (
-                               float (_out_size.width) / _input->size().width,
-                               float (_out_size.height) / _input->size().height,
-                               (*i)->area(), _subtitle_offset, _subtitle_scale
-                               );
-
-                       shared_ptr<Image> im = (*i)->image()->scale (Size (tx.w, tx.h), _scaler);
-                       prepared->alpha_blend (im, Position (tx.x, tx.y));
-               }
+               Rectangle tx = subtitle_transformed_area (
+                       float (_out_size.width) / _input->size().width,
+                       float (_out_size.height) / _input->size().height,
+                       _subtitle->area(), _subtitle_offset, _subtitle_scale
+                       );
+
+               shared_ptr<Image> im = _subtitle->image()->scale (Size (tx.w, tx.h), _scaler);
+               prepared->alpha_blend (im, Position (tx.x, tx.y));
        }
 
        create_openjpeg_container ();
@@ -310,9 +307,9 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
        asio::ip::tcp::resolver::query query (serv->host_name(), boost::lexical_cast<string> (Config::instance()->server_port ()));
        asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve (query);
 
-       Socket socket;
+       shared_ptr<Socket> socket (new Socket);
 
-       socket.connect (*endpoint_iterator, 30);
+       socket->connect (*endpoint_iterator, 30);
 
        stringstream s;
        s << "encode "
@@ -329,19 +326,27 @@ DCPVideoFrame::encode_remotely (ServerDescription const * serv)
          << Config::instance()->colour_lut_index () << " "
          << Config::instance()->j2k_bandwidth () << " ";
 
-       socket.write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
+       if (_subtitle) {
+               s << _subtitle->position().x << " " << _subtitle->position().y << " "
+                 << _subtitle->image()->size().width << " " << _subtitle->image()->size().height;
+       } else {
+               s << "-1 -1 0 0";
+       }
+
+       socket->write ((uint8_t *) s.str().c_str(), s.str().length() + 1, 30);
 
-       for (int i = 0; i < _input->components(); ++i) {
-               socket.write (_input->data()[i], _input->stride()[i] * _input->lines(i), 30);
+       _input->write_to_socket (socket);
+       if (_subtitle) {
+               _subtitle->image()->write_to_socket (socket);
        }
 
        char buffer[32];
-       socket.read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
-       socket.consume (strlen (buffer) + 1);
+       socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
+       socket->consume (strlen (buffer) + 1);
        shared_ptr<EncodedData> e (new RemotelyEncodedData (atoi (buffer)));
 
        /* now read the rest */
-       socket.read_definite_and_consume (e->data(), e->size(), 30);
+       socket->read_definite_and_consume (e->data(), e->size(), 30);
 
        _log->log (String::compose ("Finished remotely-encoded frame %1", _frame));
        
index 1f771da2df577df564a63a4198a8ce4db9a699f9..8989a938b25768298ffdaf67ca1a95aff12bedb1 100644 (file)
@@ -417,10 +417,7 @@ Decoder::process_subtitle (shared_ptr<Subtitle> s)
        _subtitle = s;
        
        if (_opt->apply_crop) {
-               list<shared_ptr<SubtitleImage> > im = _subtitle->images ();
-               for (list<shared_ptr<SubtitleImage> >::iterator i = im.begin(); i != im.end(); ++i) {
-                       Position const p = (*i)->position ();
-                       (*i)->set_position (Position (p.x - _fs->crop.left, p.y - _fs->crop.top));
-               }
+               Position const p = _subtitle->position ();
+               _subtitle->set_position (Position (p.x - _fs->crop.left, p.y - _fs->crop.top));
        }
 }
index 31af2f1c27834ac9e5b60d26f6cb9ddf8cd76e8c..636e2d546586cd42c1041ef34268ab9eb037a0f7 100644 (file)
@@ -685,20 +685,18 @@ Film::set_subtitle_scale (float s)
        signal_changed (SUBTITLE_SCALE);
 }
 
-list<pair<Position, string> >
-Film::thumb_subtitles (int n) const
+pair<Position, string>
+Film::thumb_subtitle (int n) const
 {
        string sub_file = _state.thumb_base(n) + ".sub";
        if (!filesystem::exists (sub_file)) {
-               return list<pair<Position, string> > ();
+               return pair<Position, string> ();
        }
 
        ifstream f (sub_file.c_str ());
        string line;
 
-       int sub_number;
-       int sub_x;
-       list<pair<Position, string> > subs;
+       pair<Position, string> sub;
        
        while (getline (f, line)) {
                if (line.empty ()) {
@@ -717,14 +715,13 @@ Film::thumb_subtitles (int n) const
                string const k = line.substr (0, s);
                int const v = lexical_cast<int> (line.substr(s + 1));
 
-               if (k == "image") {
-                       sub_number = v;
-               } else if (k == "x") {
-                       sub_x = v;
+               if (k == "x") {
+                       sub.first.x = v;
                } else if (k == "y") {
-                       subs.push_back (make_pair (Position (sub_x, v), String::compose ("%1.sub.%2.png", _state.thumb_base(n), sub_number)));
+                       sub.first.y = v;
+                       sub.second = String::compose ("%1.sub.png", _state.thumb_base(n));
                }
        }
 
-       return subs;
+       return sub;
 }
index c006eae36252754b21f5bcf7de806743a64f0516..46c67ad19acda2005bc795518154b8ccc0cc191c 100644 (file)
@@ -203,7 +203,7 @@ public:
        int num_thumbs () const;
        int thumb_frame (int) const;
        std::string thumb_file (int) const;
-       std::list<std::pair<Position, std::string> > thumb_subtitles (int) const;
+       std::pair<Position, std::string> thumb_subtitle (int) const;
 
        void copy_from_dvd_post_gui ();
        void examine_content ();
index f5aef84442c545d56c8d817a48228a75bea47d6a..30dc4292f40cc064172ea9363198ddd7127019c4 100644 (file)
@@ -244,6 +244,30 @@ Image::alpha_blend (shared_ptr<Image> other, Position position)
        }
 }
 
+void
+Image::read_from_socket (shared_ptr<Socket> socket)
+{
+       for (int i = 0; i < components(); ++i) {
+               uint8_t* p = data()[i];
+               for (int y = 0; y < lines(i); ++y) {
+                       socket->read_definite_and_consume (p, line_size()[i], 30);
+                       p += stride()[i];
+               }
+       }
+}
+
+void
+Image::write_to_socket (shared_ptr<Socket> socket) const
+{
+       for (int i = 0; i < components(); ++i) {
+               uint8_t* p = data()[i];
+               for (int y = 0; y < lines(i); ++y) {
+                       socket->write (p, line_size()[i], 30);
+                       p += stride()[i];
+               }
+       }
+}
+
 /** Construct a SimpleImage of a given size and format, allocating memory
  *  as required.
  *
index 30c8519e7ee8317a3cf78f4d2da8f594dc67f7d6..6c3c9109e0bb8ebad478403724bf43969359211f 100644 (file)
@@ -75,6 +75,9 @@ public:
        void alpha_blend (boost::shared_ptr<Image> image, Position pos);
        
        void make_black ();
+
+       void read_from_socket (boost::shared_ptr<Socket>);
+       void write_to_socket (boost::shared_ptr<Socket>) const;
        
        PixelFormat pixel_format () const {
                return _pixel_format;
index 9bd8162f89406cf54b446f0d7e290817d223bfbb..d0feb18934e7b8b8d8ff27f661af2a061c072a75 100644 (file)
@@ -69,32 +69,23 @@ ImageMagickEncoder::process_video (shared_ptr<Image> image, int frame, shared_pt
                string tmp_metadata_file = _opt->frame_out_path (frame, false, ".sub");
                ofstream metadata (tmp_metadata_file.c_str ());
                
-               list<shared_ptr<SubtitleImage> > images = sub->images ();
-               int n = 0;
-               for (list<shared_ptr<SubtitleImage> >::iterator i = images.begin(); i != images.end(); ++i) {
-                       stringstream ext;
-                       ext << ".sub." << n << ".png";
-
-                       Size new_size = (*i)->image()->size ();
-                       new_size.width *= x_scale;
-                       new_size.height *= y_scale;
-                       shared_ptr<Image> scaled = (*i)->image()->scale (new_size, _fs->scaler);
-                       shared_ptr<Image> compact (new CompactImage (scaled));
-                       
-                       string tmp_sub_file = _opt->frame_out_path (frame, true, ext.str ());
-                       Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]);
-                       sub_thumb.magick ("PNG");
-                       sub_thumb.write (tmp_sub_file);
-                       filesystem::rename (tmp_sub_file, _opt->frame_out_path (frame, false, ext.str ()));
-
-                       metadata << "image " << n << "\n"
-                                << "x " << (*i)->position().x << "\n"
-                                << "y " << (*i)->position().y << "\n";
+               Size new_size = sub->image()->size ();
+               new_size.width *= x_scale;
+               new_size.height *= y_scale;
+               shared_ptr<Image> scaled = sub->image()->scale (new_size, _fs->scaler);
+               shared_ptr<Image> compact (new CompactImage (scaled));
+               
+               string tmp_sub_file = _opt->frame_out_path (frame, true, ".sub.png");
+               Magick::Image sub_thumb (compact->size().width, compact->size().height, "RGBA", MagickCore::CharPixel, compact->data()[0]);
+               sub_thumb.magick ("PNG");
+               sub_thumb.write (tmp_sub_file);
+               filesystem::rename (tmp_sub_file, _opt->frame_out_path (frame, false, ".sub.png"));
 
-                       metadata.close ();
-                       filesystem::rename (tmp_metadata_file, _opt->frame_out_path (frame, false, ".sub"));
-               }
+               metadata << "x " << sub->position().x << "\n"
+                        << "y " << sub->position().y << "\n";
 
+               metadata.close ();
+               filesystem::rename (tmp_metadata_file, _opt->frame_out_path (frame, false, ".sub.png"));
        }
        
        frame_done (frame);
index b5eda2eb8fa537d3d285dcd2d073d8490e34d52c..2d19d06be79e9783941569c48b462f50eff73a9a 100644 (file)
@@ -33,6 +33,7 @@
 #include "image.h"
 #include "dcp_video_frame.h"
 #include "config.h"
+#include "subtitle.h"
 
 using namespace std;
 using namespace boost;
@@ -72,18 +73,18 @@ Server::Server (Log* log)
 int
 Server::process (shared_ptr<Socket> socket)
 {
-       char buffer[128];
+       char buffer[256];
        socket->read_indefinite ((uint8_t *) buffer, sizeof (buffer), 30);
        socket->consume (strlen (buffer) + 1);
        
        stringstream s (buffer);
-       
+
        string command;
        s >> command;
        if (command != "encode") {
                return -1;
        }
-       
+
        Size in_size;
        int pixel_format_int;
        Size out_size;
@@ -96,6 +97,8 @@ Server::process (shared_ptr<Socket> socket)
        string post_process;
        int colour_lut_index;
        int j2k_bandwidth;
+       Position subtitle_position;
+       Size subtitle_size;
        
        s >> in_size.width >> in_size.height
          >> pixel_format_int
@@ -108,8 +111,10 @@ Server::process (shared_ptr<Socket> socket)
          >> frames_per_second
          >> post_process
          >> colour_lut_index
-         >> j2k_bandwidth;
-       
+         >> j2k_bandwidth
+         >> subtitle_position.x >> subtitle_position.y
+         >> subtitle_size.width >> subtitle_size.height;
+
        PixelFormat pixel_format = (PixelFormat) pixel_format_int;
        Scaler const * scaler = Scaler::from_id (scaler_id);
        if (post_process == "none") {
@@ -117,20 +122,24 @@ Server::process (shared_ptr<Socket> socket)
        }
        
        shared_ptr<Image> image (new AlignedImage (pixel_format, in_size));
-       
-       for (int i = 0; i < image->components(); ++i) {
-               socket->read_definite_and_consume (image->data()[i], image->stride()[i] * image->lines(i), 30);
+
+       image->read_from_socket (socket);
+
+       shared_ptr<Subtitle> sub;
+       if (subtitle_position.x != -1) {
+               shared_ptr<Image> subtitle_image (new AlignedImage (pixel_format, subtitle_size));
+               subtitle_image->read_from_socket (socket);
+               sub.reset (new Subtitle (subtitle_position, subtitle_image));
        }
 
-       /* XXX: subtitle */
        DCPVideoFrame dcp_video_frame (
-               image, shared_ptr<Subtitle> (), out_size, padding, subtitle_offset, subtitle_scale,
+               image, sub, out_size, padding, subtitle_offset, subtitle_scale,
                scaler, frame, frames_per_second, post_process, colour_lut_index, j2k_bandwidth, _log
                );
        
        shared_ptr<EncodedData> encoded = dcp_video_frame.encode_locally ();
        encoded->send (socket);
-       
+
        return frame;
 }
 
index 0eb40b14e83229abab3c8dd8c3a0148c6bffda6f..2e9a890880ac9239dcd06f7b3ee12ab742feeaed 100644 (file)
@@ -34,25 +34,18 @@ Subtitle::Subtitle (AVSubtitle const & sub)
        _from = packet_time + (double (sub.start_display_time) / 1e3);
        _to = packet_time + (double (sub.end_display_time) / 1e3);
 
-       for (unsigned int i = 0; i < sub.num_rects; ++i) {
-               _images.push_back (shared_ptr<SubtitleImage> (new SubtitleImage (sub.rects[i])));
+       if (sub.num_rects > 1) {
+               throw DecodeError ("multi-part subtitles not yet supported");
        }
-}
 
-/** @param t Time in seconds from the start of the film */
-bool
-Subtitle::displayed_at (double t)
-{
-       return t >= _from && t <= _to;
-}
+       AVSubtitleRect const * rect = sub.rects[0];
 
-SubtitleImage::SubtitleImage (AVSubtitleRect const * rect)
-       : _position (rect->x, rect->y)
-       , _image (new AlignedImage (PIX_FMT_RGBA, Size (rect->w, rect->h)))
-{
        if (rect->type != SUBTITLE_BITMAP) {
                throw DecodeError ("non-bitmap subtitles not yet supported");
        }
+       
+       _position = Position (rect->x, rect->y);
+       _image.reset (new AlignedImage (PIX_FMT_RGBA, Size (rect->w, rect->h)));
 
        /* Start of the first line in the subtitle */
        uint8_t* sub_p = rect->pict.data[0];
@@ -72,8 +65,24 @@ SubtitleImage::SubtitleImage (AVSubtitleRect const * rect)
        }
 }
 
+Subtitle::Subtitle (Position p, shared_ptr<Image> i)
+       : _from (0)
+       , _to (0)
+       , _position (p)
+       , _image (i)
+{
+
+}
+       
+/** @param t Time in seconds from the start of the film */
+bool
+Subtitle::displayed_at (double t)
+{
+       return t >= _from && t <= _to;
+}
+
 Rectangle
-transformed_subtitle_area (
+subtitle_transformed_area (
        float target_x_scale, float target_y_scale,
        Rectangle sub_area, int subtitle_offset, float subtitle_scale
        )
@@ -107,7 +116,7 @@ transformed_subtitle_area (
 }
 
 Rectangle
-SubtitleImage::area () const
+Subtitle::area () const
 {
        return Rectangle (_position.x, _position.y, _image->size().width, _image->size().height);
 }
index 6fd0d8772643fe3ca8d92d27238a452b53bd867e..ef9571ad4f6a0f849b80ac61ce54858a77399b9e 100644 (file)
 #include "util.h"
 
 struct AVSubtitle;
-class SubtitleImage;
 class Image;
-class FilmState;
 
 class Subtitle
 {
 public:
        Subtitle (AVSubtitle const &);
+       Subtitle (Position p, boost::shared_ptr<Image> i);
 
        bool displayed_at (double t);
 
-       std::list<boost::shared_ptr<SubtitleImage> > images () const {
-               return _images;
-       }
-
-private:
-       /** display from time in seconds from the start of the film */
-       double _from;
-       /** display to time in seconds from the start of the film */
-       double _to;
-       std::list<boost::shared_ptr<SubtitleImage> > _images;
-};
-
-extern Rectangle transformed_subtitle_area (
-       float target_x_scale, float target_y_scale,
-       Rectangle sub_area, int subtitle_offset, float subtitle_scale
-       );
-
-class SubtitleImage
-{
-public:
-       SubtitleImage (AVSubtitleRect const *);
-
        void set_position (Position p) {
                _position = p;
        }
@@ -68,8 +45,19 @@ public:
        }
 
        Rectangle area () const;
-
+       
 private:
+       /** display from time in seconds from the start of the film */
+       double _from;
+       /** display to time in seconds from the start of the film */
+       double _to;
        Position _position;
        boost::shared_ptr<Image> _image;
 };
+
+Rectangle
+subtitle_transformed_area (
+       float target_x_scale, float target_y_scale,
+       Rectangle sub_area, int subtitle_offset, float subtitle_scale
+       );
+       
index bd7675a8af28ae05676aba327794ef37b2b6105f..916f47cdb9bd62733b8823ba657a7b0b1b51353a 100644 (file)
@@ -186,7 +186,7 @@ private:
        boost::asio::deadline_timer _deadline;
        boost::asio::ip::tcp::socket _socket;
        /** a buffer for small reads */
-       uint8_t _buffer[256];
+       uint8_t _buffer[512];
        /** amount of valid data in the buffer */
        int _buffer_data;
 };
index bf082adc2e534824eb6d769cc32fa5c43cb2be85..34140d16c19d9afb78620510d2707e930fef7220 100644 (file)
@@ -58,21 +58,18 @@ public:
                if (_frame_rebuild_needed) {
                        _image.reset (new wxImage (std_to_wx (_film->thumb_file (_index))));
 
-                       _subtitles.clear ();
-                       list<pair<Position, string> > s = _film->thumb_subtitles (_index);
-                       for (list<pair<Position, string> >::iterator i = s.begin(); i != s.end(); ++i) {
-                               _subtitles.push_back (SubtitleView (i->first, std_to_wx (i->second)));
+                       _subtitle.reset ();
+                       pair<Position, string> s = _film->thumb_subtitle (_index);
+                       if (!s.second.empty ()) {
+                               _subtitle.reset (new SubtitleView (s.first, std_to_wx (s.second)));
                        }
 
                        _frame_rebuild_needed = false;
-
                        compose ();
-                       _composition_needed = false;
                }
 
                if (_composition_needed) {
                        compose ();
-                       _composition_needed = false;
                }
 
                wxPaintDC dc (this);
@@ -80,10 +77,8 @@ public:
                        dc.DrawBitmap (*_bitmap, 0, 0, false);
                }
 
-               if (_film->with_subtitles ()) {
-                       for (list<SubtitleView>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
-                               dc.DrawBitmap (*i->bitmap, i->transformed_area.x, i->transformed_area.y, true);
-                       }
+               if (_film->with_subtitles() && _subtitle) {
+                       dc.DrawBitmap (*_subtitle->bitmap, _subtitle->transformed_area.x, _subtitle->transformed_area.y, true);
                }
        }
 
@@ -123,7 +118,7 @@ public:
        {
                _bitmap.reset ();
                _image.reset ();
-               _subtitles.clear ();
+               _subtitle.reset ();
        }
 
        void recompose ()
@@ -138,6 +133,8 @@ private:
 
        void compose ()
        {
+               _composition_needed = false;
+               
                if (!_film || !_image) {
                        return;
                }
@@ -176,17 +173,17 @@ private:
 
                _bitmap.reset (new wxBitmap (_transformed_image));
 
-               for (list<SubtitleView>::iterator i = _subtitles.begin(); i != _subtitles.end(); ++i) {
+               if (_subtitle) {
 
-                       i->transformed_area = transformed_subtitle_area (
-                               x_scale, y_scale, i->base_area, _film->subtitle_offset(), _film->subtitle_scale()
+                       _subtitle->transformed_area = subtitle_transformed_area (
+                               x_scale, y_scale, _subtitle->base_area, _film->subtitle_offset(), _film->subtitle_scale()
                                );
 
-                       i->transformed_image = i->base_image;
-                       i->transformed_image.Rescale (i->transformed_area.w, i->transformed_area.h, wxIMAGE_QUALITY_HIGH);
-                       i->transformed_area.x -= _film->crop().left;
-                       i->transformed_area.y -= _film->crop().top;
-                       i->bitmap.reset (new wxBitmap (i->transformed_image));
+                       _subtitle->transformed_image = _subtitle->base_image;
+                       _subtitle->transformed_image.Rescale (_subtitle->transformed_area.w, _subtitle->transformed_area.h, wxIMAGE_QUALITY_HIGH);
+                       _subtitle->transformed_area.x -= _film->crop().left;
+                       _subtitle->transformed_area.y -= _film->crop().top;
+                       _subtitle->bitmap.reset (new wxBitmap (_subtitle->transformed_image));
                }
        }
 
@@ -217,7 +214,7 @@ private:
                shared_ptr<wxBitmap> bitmap;
        };
 
-       list<SubtitleView> _subtitles;
+       shared_ptr<SubtitleView> _subtitle;
 };
 
 BEGIN_EVENT_TABLE (ThumbPanel, wxPanel)
index c43f0cb7db4328a1b43c3dc8b67476b794525678..a7023b8411d47b7fbbf6643ac40f4ed22cd6fbb3 100644 (file)
 using namespace std;
 using namespace boost;
 
+void
+setup_test_config ()
+{
+       Config::instance()->set_num_local_encoding_threads (1);
+       Config::instance()->set_colour_lut_index (0);
+       Config::instance()->set_j2k_bandwidth (200000000);
+       Config::instance()->set_servers (vector<ServerDescription*> ());
+       Config::instance()->set_server_port (61920);
+}
+
 BOOST_AUTO_TEST_CASE (film_metadata_test)
 {
        dvdomatic_setup ();
+       setup_test_config ();
        
        string const test_film = "build/test/film";
        
@@ -258,7 +269,7 @@ BOOST_AUTO_TEST_CASE (paths_test)
 }
 
 void
-do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded)
+do_remote_encode (shared_ptr<DCPVideoFrame> frame, ServerDescription* description, shared_ptr<EncodedData> locally_encoded, int N)
 {
        shared_ptr<EncodedData> remotely_encoded;
        BOOST_CHECK_NO_THROW (remotely_encoded = frame->encode_remotely (description));
@@ -303,8 +314,8 @@ BOOST_AUTO_TEST_CASE (client_server_test)
                );
 
        shared_ptr<EncodedData> locally_encoded = frame->encode_locally ();
+       BOOST_ASSERT (locally_encoded);
        
-       Config::instance()->set_server_port (61920);
        Server* server = new Server (&log);
 
        new thread (boost::bind (&Server::run, server, 2));
@@ -316,12 +327,16 @@ BOOST_AUTO_TEST_CASE (client_server_test)
 
        list<thread*> threads;
        for (int i = 0; i < 8; ++i) {
-               threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded)));
+               threads.push_back (new thread (boost::bind (do_remote_encode, frame, &description, locally_encoded, i)));
        }
 
        for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
                (*i)->join ();
        }
+
+       for (list<thread*>::iterator i = threads.begin(); i != threads.end(); ++i) {
+               delete *i;
+       }
 }
 
 BOOST_AUTO_TEST_CASE (make_dcp_test)